diff options
| author | Geremy Condra <gcondra@google.com> | 2011-10-12 18:17:24 -0700 |
|---|---|---|
| committer | Geremy Condra <gcondra@google.com> | 2011-10-12 18:17:24 -0700 |
| commit | 03ebf06f4e1112a0e9533b93062d169232c4cbfe (patch) | |
| tree | 6820146aa5f11e9eed02837bb434c2b485d289e8 /src | |
| parent | 5155f1c7438ef540d7b25eb70aa1639579795b07 (diff) | |
| download | platform_external_mksh-03ebf06f4e1112a0e9533b93062d169232c4cbfe.tar.gz platform_external_mksh-03ebf06f4e1112a0e9533b93062d169232c4cbfe.tar.bz2 platform_external_mksh-03ebf06f4e1112a0e9533b93062d169232c4cbfe.zip | |
Updated mksh to ToT as of 12 October 2011.android-sdk-adt_r20tools_r20ics-plus-aosp
This includes several security fixes and brings us in
line with upstream, who has included fixes for a
number of issues originally reported by the Android
team.
Change-Id: I1e0f3adf292b86fa7679b3364a774e5b6004beb8
Diffstat (limited to 'src')
| -rw-r--r-- | src/Build.sh | 409 | ||||
| -rw-r--r-- | src/Makefile | 97 | ||||
| -rw-r--r-- | src/check.pl | 71 | ||||
| -rw-r--r-- | src/check.t | 2023 | ||||
| -rw-r--r-- | src/dot.mkshrc | 375 | ||||
| -rw-r--r-- | src/edit.c | 757 | ||||
| -rw-r--r-- | src/eval.c | 376 | ||||
| -rw-r--r-- | src/exec.c | 674 | ||||
| -rw-r--r-- | src/expr.c | 179 | ||||
| -rw-r--r-- | src/funcs.c | 1711 | ||||
| -rw-r--r-- | src/histrap.c | 267 | ||||
| -rw-r--r-- | src/jobs.c | 481 | ||||
| -rw-r--r-- | src/lalloc.c | 40 | ||||
| -rw-r--r-- | src/lex.c | 937 | ||||
| -rw-r--r-- | src/main.c | 791 | ||||
| -rw-r--r-- | src/misc.c | 1013 | ||||
| -rw-r--r-- | src/mksh.1 | 6280 | ||||
| -rw-r--r-- | src/sh.h | 689 | ||||
| -rw-r--r-- | src/sh_flags.h | 8 | ||||
| -rw-r--r-- | src/shf.c | 365 | ||||
| -rw-r--r-- | src/strlcpy.c | 52 | ||||
| -rw-r--r-- | src/syn.c | 321 | ||||
| -rw-r--r-- | src/tree.c | 762 | ||||
| -rw-r--r-- | src/var.c | 571 | ||||
| -rw-r--r-- | src/var_spec.h | 4 |
25 files changed, 15228 insertions, 4025 deletions
diff --git a/src/Build.sh b/src/Build.sh index c98b1ca..6561cf6 100644 --- a/src/Build.sh +++ b/src/Build.sh @@ -1,7 +1,7 @@ #!/bin/sh -srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $' +srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $' #- -# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 # Thorsten Glaser <tg@mirbsd.org> # # Provided that these terms and disclaimer and all copyright notices @@ -30,6 +30,8 @@ srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $' # MKSH_NOPWNAM MKSH_NO_LIMITS MKSH_SMALL MKSH_S_NOVI # MKSH_UNEMPLOYED MKSH_DEFAULT_EXECSHELL MKSHRC_PATH # MKSH_DEFAULT_TMPDIR MKSH_CLRTOEOL_STRING MKSH_A4PB +# MKSH_NO_DEPRECATED_WARNING MKSH_DONT_EMIT_IDSTRING +# MKSH_NOPROSPECTOFWORK MKSH_NO_EXTERNAL_CAT LC_ALL=C export LC_ALL @@ -204,11 +206,15 @@ EOF test x"$fv" = x"1" } +add_cppflags() { + CPPFLAGS="$CPPFLAGS $*" +} + ac_cppflags() { test x"$1" = x"" || fu=$1 fv=$2 test x"$2" = x"" && eval fv=\$HAVE_$fu - CPPFLAGS="$CPPFLAGS -DHAVE_$fu=$fv" + add_cppflags -DHAVE_$fu=$fv } ac_test() { @@ -216,7 +222,7 @@ ac_test() { ac_cppflags } -# ac_flags [-] add varname flags [text] +# ac_flags [-] add varname cflags [text] [ldflags] ac_flags() { if test x"$1" = x"-"; then shift @@ -228,9 +234,14 @@ ac_flags() { vn=$2 f=$3 ft=$4 + fl=$5 test x"$ft" = x"" && ft="if $f can be used" save_CFLAGS=$CFLAGS CFLAGS="$CFLAGS $f" + if test -n "$fl"; then + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $fl" + fi if test 1 = $hf; then ac_testn can_$vn '' "$ft" else @@ -240,6 +251,9 @@ ac_flags() { EOF fi eval fv=\$HAVE_CAN_`upper $vn` + if test -n "$fl"; then + test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS + fi test 11 = $fa$fv || CFLAGS=$save_CFLAGS } @@ -287,7 +301,7 @@ rmf a.exe* a.out* conftest.c *core lft mksh* no *.bc *.ll *.o \ Rebuild.sh signames.inc test.sh x vv.out curdir=`pwd` srcdir=`dirname "$0"` check_categories= -test -n "$dirname" || dirname=. +test -n "$srcdir" || srcdir=. dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h` e=echo @@ -301,7 +315,7 @@ last= for i do case $last:$i in - c:combine|c:dragonegg|c:llvm) + c:combine|c:dragonegg|c:llvm|c:lto) cm=$i last= ;; @@ -316,28 +330,14 @@ do :-c) last=c ;; - :-combine) - cm=combine - echo "$me: Warning: '$i' is deprecated, use '-c combine' instead!" >&2 - ;; :-g) # checker, debug, valgrind build - CPPFLAGS="$CPPFLAGS -DDEBUG" + add_cppflags -DDEBUG CFLAGS="$CFLAGS -g3 -fno-builtin" ;; :-j) pm=1 ;; - :-llvm) - cm=llvm - optflags=-std-compile-opts - echo "$me: Warning: '$i' is deprecated, use '-c llvm -O' instead!" >&2 - ;; - :-llvm=*) - cm=llvm - optflags=`echo "x$i" | sed 's/^x-llvm=//'` - echo "$me: Warning: '$i' is deprecated, use '-c llvm -o $llvm' instead!" >&2 - ;; :-M) cm=makefile ;; @@ -383,12 +383,22 @@ else fi test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname` +if test x"$TARGET_OS" = x""; then + echo "$me: Set TARGET_OS, your uname is broken!" >&2 + exit 1 +fi oswarn= ccpc=-Wc, ccpl=-Wl, tsts= ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done' +# Evil hack +if test x"$TARGET_OS" = x"Android"; then + check_categories="$check_categories android" + TARGET_OS=Linux +fi + # Configuration depending on OS revision, on OSes that need them case $TARGET_OS in QNX) @@ -399,7 +409,7 @@ esac # Configuration depending on OS name case $TARGET_OS in AIX) - CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE" + add_cppflags -D_ALL_SOURCE : ${HAVE_SETLOCALE_CTYPE=0} ;; BeOS) @@ -417,22 +427,34 @@ DragonFly) ;; FreeBSD) ;; +FreeMiNT) + oswarn="; it has minor issues" + add_cppflags -D_GNU_SOURCE + : ${HAVE_SETLOCALE_CTYPE=0} + ;; GNU) + case $CC in + *tendracc*) ;; + *) add_cppflags -D_GNU_SOURCE ;; + esac # define NO_PATH_MAX to use Hurd-only functions - CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -DNO_PATH_MAX" + add_cppflags -DNO_PATH_MAX ;; GNU/kFreeBSD) - CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + case $CC in + *tendracc*) ;; + *) add_cppflags -D_GNU_SOURCE ;; + esac ;; Haiku) - CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8" + add_cppflags -DMKSH_ASSUME_UTF8 ;; HP-UX) ;; Interix) ccpc='-X ' ccpl='-Y ' - CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE" + add_cppflags -D_ALL_SOURCE : ${LIBS='-lcrypt'} : ${HAVE_SETLOCALE_CTYPE=0} ;; @@ -440,19 +462,29 @@ IRIX*) : ${HAVE_SETLOCALE_CTYPE=0} ;; Linux) - CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + case $CC in + *tendracc*) ;; + *) add_cppflags -D_GNU_SOURCE ;; + esac + add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN : ${HAVE_REVOKE=0} ;; MidnightBSD) ;; Minix) - CPPFLAGS="$CPPFLAGS -DMKSH_UNEMPLOYED -DMKSH_CONSERVATIVE_FDS" - CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX" + add_cppflags -DMKSH_UNEMPLOYED + add_cppflags -DMKSH_CONSERVATIVE_FDS + add_cppflags -DMKSH_NO_LIMITS + add_cppflags -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX oldish_ed=no-stderr-ed # /usr/bin/ed(!) is broken : ${HAVE_SETLOCALE_CTYPE=0} ;; MirBSD) ;; +MSYS_*) + # probably same as CYGWIN* – need to test; from RT|Chatzilla + oswarn='but will probably work' + ;; NetBSD) ;; OpenBSD) @@ -460,15 +492,20 @@ OpenBSD) ;; OSF1) HAVE_SIG_T=0 # incompatible - CPPFLAGS="$CPPFLAGS -D_OSF_SOURCE -D_POSIX_C_SOURCE=200112L" - CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED" + add_cppflags -D_OSF_SOURCE + add_cppflags -D_POSIX_C_SOURCE=200112L + add_cppflags -D_XOPEN_SOURCE=600 + add_cppflags -D_XOPEN_SOURCE_EXTENDED : ${HAVE_SETLOCALE_CTYPE=0} ;; Plan9) - CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_LIMITS_EXTENSION" - CPPFLAGS="$CPPFLAGS -D_BSD_EXTENSION -D_SUSV2_SOURCE" + add_cppflags -D_POSIX_SOURCE + add_cppflags -D_LIMITS_EXTENSION + add_cppflags -D_BSD_EXTENSION + add_cppflags -D_SUSV2_SOURCE + add_cppflags -DMKSH_ASSUME_UTF8 oswarn=' and will currently not work' - CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8 -DMKSH_UNEMPLOYED" + add_cppflags -DMKSH_UNEMPLOYED ;; PW32*) HAVE_SIG_T=0 # incompatible @@ -476,7 +513,7 @@ PW32*) : ${HAVE_SETLOCALE_CTYPE=0} ;; QNX) - CPPFLAGS="$CPPFLAGS -D__NO_EXT_QNX" + add_cppflags -D__NO_EXT_QNX case $TARGET_OSREV in [012345].*|6.[0123].*|6.4.[01]) oldish_ed=no-stderr-ed # oldish /bin/ed is broken @@ -485,15 +522,16 @@ QNX) : ${HAVE_SETLOCALE_CTYPE=0} ;; SunOS) - CPPFLAGS="$CPPFLAGS -D_BSD_SOURCE -D__EXTENSIONS__" + add_cppflags -D_BSD_SOURCE + add_cppflags -D__EXTENSIONS__ ;; syllable) - CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + add_cppflags -D_GNU_SOURCE oswarn=' and will currently not work' ;; ULTRIX) : ${CC=cc -YPOSIX} - CPPFLAGS="$CPPFLAGS -Dssize_t=int" + add_cppflags -Dssize_t=int : ${HAVE_SETLOCALE_CTYPE=0} ;; UWIN*) @@ -509,6 +547,8 @@ UWIN*) ;; esac +: ${HAVE_MKNOD=0} + : ${CC=cc} ${NROFF=nroff} test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \ NROFF="$NROFF -c" @@ -516,6 +556,10 @@ test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \ # this aids me in tracing FTBFSen without access to the buildd $e "Hi from$ao $bi$srcversion$ao on:" case $TARGET_OS in +AIX) + vv '|' "oslevel >&2" + vv '|' "uname -a >&2" + ;; Darwin) vv '|' "hwprefs machine_type os_type os_class >&2" vv '|' "uname -a >&2" @@ -735,6 +779,7 @@ watcom) own risk, please report success/failure to the developers.' ;; xlc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion" vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose" vv '|' "ld -V" ;; @@ -866,9 +911,46 @@ if test $ct = gcc; then ac_flags 1 fnostrictaliasing -fno-strict-aliasing ac_flags 1 fstackprotectorall -fstack-protector-all ac_flags 1 fwrapv -fwrapv - test $cm = combine && ac_flags 0 combine \ - '-fwhole-program --combine' \ - 'if gcc supports -fwhole-program --combine' + test $cm = dragonegg && case " $CC $CFLAGS $LDFLAGS " in + *\ -fplugin=*dragonegg*) ;; + *) ac_flags 1 fplugin_dragonegg -fplugin=dragonegg ;; + esac + if test $cm = lto; then + fv=0 + checks='1 2 3 4 5 6 7 8' + elif test $cm = combine; then + fv=0 + checks='7 8' + else + fv=1 + fi + test $fv = 1 || for what in $checks; do + test $fv = 1 && break + case $what in + 1) t_cflags='-flto=jobserver' + t_ldflags='-fuse-linker-plugin' + t_use=1 t_name=fltojs_lp ;; + 2) t_cflags='-flto=jobserver' t_ldflags='' + t_use=1 t_name=fltojs_nn ;; + 3) t_cflags='-flto=jobserver' + t_ldflags='-fno-use-linker-plugin -fwhole-program' + t_use=1 t_name=fltojs_np ;; + 4) t_cflags='-flto' + t_ldflags='-fuse-linker-plugin' + t_use=1 t_name=fltons_lp ;; + 5) t_cflags='-flto' t_ldflags='' + t_use=1 t_name=fltons_nn ;; + 6) t_cflags='-flto' + t_ldflags='-fno-use-linker-plugin -fwhole-program' + t_use=1 t_name=fltons_np ;; + 7) t_cflags='-fwhole-program --combine' t_ldflags='' + t_use=0 t_name=combine cm=combine ;; + 8) fv=1 cm=normal ;; + esac + test $fv = 1 && break + ac_flags $t_use $t_name "$t_cflags" \ + "if gcc supports $t_cflags $t_ldflags" "$t_ldflags" + done i=1 elif test $ct = icc; then ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode @@ -933,8 +1015,8 @@ if test 1 = $i; then ac_flags 1 stdc99 -std=c99 'for support of ISO C99' ac_flags 1 wall -Wall fi -phase=x +phase=x # The following tests run with -Werror or similar (all compilers) if possible NOWARN=$DOWARN test $ct = pcc && phase=u @@ -942,76 +1024,77 @@ test $ct = pcc && phase=u # # Compiler: check for stuff that only generates warnings # -ac_test attribute_bounded '' 'for __attribute__((bounded))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_bounded '' 'for __attribute__((__bounded__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else #include <string.h> #undef __attribute__ int xcopy(const void *, void *, size_t) - __attribute__((bounded (buffer, 1, 3))) - __attribute__((bounded (buffer, 2, 3))); + __attribute__((__bounded__ (__buffer__, 1, 3))) + __attribute__((__bounded__ (__buffer__, 2, 3))); int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); } int xcopy(const void *s, void *d, size_t n) { memmove(d, s, n); return ((int)n); } #endif EOF -ac_test attribute_format '' 'for __attribute__((format))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_format '' 'for __attribute__((__format__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else + #define fprintf printfoo #include <stdio.h> #undef __attribute__ - #undef printf - extern int printf(const char *format, ...) - __attribute__((format (printf, 1, 2))); - int main(int ac, char **av) { return (printf("%s%d", *av, ac)); } + #undef fprintf + extern int fprintf(FILE *, const char *format, ...) + __attribute__((__format__ (__printf__, 2, 3))); + int main(int ac, char **av) { return (fprintf(stderr, "%s%d", *av, ac)); } #endif EOF -ac_test attribute_nonnull '' 'for __attribute__((nonnull))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_nonnull '' 'for __attribute__((__nonnull__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else - int foo(char *s1, char *s2) __attribute__((nonnull)); - int bar(char *s1, char *s2) __attribute__((nonnull (1, 2))); - int baz(char *s) __attribute__((nonnull (1))); + int foo(char *s1, char *s2) __attribute__((__nonnull__)); + int bar(char *s1, char *s2) __attribute__((__nonnull__ (1, 2))); + int baz(char *s) __attribute__((__nonnull__ (1))); int foo(char *s1, char *s2) { return (bar(s2, s1)); } int bar(char *s1, char *s2) { return (baz(s1) - baz(s2)); } int baz(char *s) { return (*s); } int main(int ac, char **av) { return (ac == foo(av[0], av[ac-1])); } #endif EOF -ac_test attribute_noreturn '' 'for __attribute__((noreturn))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_noreturn '' 'for __attribute__((__noreturn__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else #include <stdlib.h> #undef __attribute__ - void fnord(void) __attribute__((noreturn)); + void fnord(void) __attribute__((__noreturn__)); int main(void) { fnord(); } void fnord(void) { exit(0); } #endif EOF -ac_test attribute_unused '' 'for __attribute__((unused))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_unused '' 'for __attribute__((__unused__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else - int main(int ac __attribute__((unused)), char **av - __attribute__((unused))) { return (0); } + int main(int ac __attribute__((__unused__)), char **av + __attribute__((__unused__))) { return (0); } #endif EOF -ac_test attribute_used '' 'for __attribute__((used))' <<-'EOF' - #if defined(__GNUC__) && (__GNUC__ < 2) - /* force a failure: gcc 1.42 has a false positive here */ +ac_test attribute_used '' 'for __attribute__((__used__))' <<-'EOF' + #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) + /* force a failure: TenDRA and gcc 1.42 have false positive here */ int main(void) { return (thiswillneverbedefinedIhope()); } #else - static const char fnord[] __attribute__((used)) = "42"; + static const char fnord[] __attribute__((__used__)) = "42"; int main(void) { return (0); } #endif EOF @@ -1043,43 +1126,93 @@ if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \ ;; esac - : ${HAVE_MKNOD=0} : ${HAVE_NICE=0} - : ${HAVE_REVOKE=0} : ${HAVE_PERSISTENT_HISTORY=0} - check_categories=$check_categories,smksh + check_categories="$check_categories smksh" HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1 # from sh.h fi ac_ifcpp 'ifdef MKSH_BINSHREDUCED' isset_MKSH_BINSHREDUCED '' \ "if a reduced-feature sh is requested" && \ - check_categories=$check_categories,binsh + check_categories="$check_categories binsh" ac_ifcpp 'ifdef MKSH_UNEMPLOYED' isset_MKSH_UNEMPLOYED '' \ "if mksh will be built without job control" && \ - check_categories=$check_categories,arge + check_categories="$check_categories arge" +ac_ifcpp 'ifdef MKSH_NOPROSPECTOFWORK' isset_MKSH_NOPROSPECTOFWORK '' \ + "if mksh will be built without job signals" && \ + check_categories="$check_categories arge nojsig" ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \ 'if the default UTF-8 mode is specified' && : ${HAVE_SETLOCALE_CTYPE=0} ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \ 'if traditional/conservative fd use is requested' && \ - check_categories=$check_categories,convfds + check_categories="$check_categories convfds" # # Environment: headers # -ac_header sys/param.h +ac_header sys/bsdtypes.h +ac_header sys/file.h sys/types.h ac_header sys/mkdev.h sys/types.h ac_header sys/mman.h sys/types.h +ac_header sys/param.h +ac_header sys/select.h sys/types.h ac_header sys/sysmacros.h +ac_header bstring.h ac_header grp.h sys/types.h ac_header libgen.h ac_header libutil.h sys/types.h ac_header paths.h -ac_header stdbool.h ac_header stdint.h stdarg.h -ac_header strings.h sys/types.h +# include strings.h only if compatible with string.h +ac_header strings.h sys/types.h string.h ac_header ulimit.h sys/types.h ac_header values.h # +# check whether whatever we use for the final link will succeed +# +if test $cm = makefile; then + : nothing to check +else + HAVE_LINK_WORKS=x + ac_testinit link_works '' 'checking if the final link command may succeed' + fv=1 + cat >conftest.c <<-'EOF' + #define EXTERN + #define MKSH_INCLUDES_ONLY + #include "sh.h" + __RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $"); + int main(void) { printf("Hello, World!\n"); return (0); } +EOF + case $cm in + llvm) + v "$CC $CFLAGS $CPPFLAGS $NOWARN -emit-llvm -c conftest.c" || fv=0 + rmf mksh.s + test $fv = 0 || v "llvm-link -o - conftest.o | opt $optflags | llc -o mksh.s" || fv=0 + test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr" + ;; + dragonegg) + v "$CC $CFLAGS $CPPFLAGS $NOWARN -S -flto conftest.c" || fv=0 + test $fv = 0 || v "mv conftest.s conftest.ll" + test $fv = 0 || v "llvm-as conftest.ll" || fv=0 + rmf mksh.s + test $fv = 0 || v "llvm-link -o - conftest.bc | opt $optflags | llc -o mksh.s" || fv=0 + test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr" + ;; + combine) + v "$CC $CFLAGS $CPPFLAGS $LDFLAGS -fwhole-program --combine $NOWARN -o $tcfn conftest.c $LIBS $ccpr" + ;; + lto|normal) + cm=normal + v "$CC $CFLAGS $CPPFLAGS $NOWARN -c conftest.c" || fv=0 + test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn conftest.o $LIBS $ccpr" + ;; + esac + test -f $tcfn || fv=0 + ac_testdone + test $fv = 1 || exit 1 +fi + +# # Environment: definitions # echo '#include <sys/types.h> @@ -1090,10 +1223,11 @@ int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && int main(void) { return (0); }' >lft.c ac_testn can_lfs '' "for large file support" <lft.c save_CPPFLAGS=$CPPFLAGS -CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64" +add_cppflags -D_FILE_OFFSET_BITS=64 ac_testn can_lfs_sus '!' can_lfs 0 "... with -D_FILE_OFFSET_BITS=64" <lft.c if test 0 = $HAVE_CAN_LFS_SUS; then - CPPFLAGS="$save_CPPFLAGS -D_LARGE_FILES=1" + CPPFLAGS=$save_CPPFLAGS + add_cppflags -D_LARGE_FILES=1 ac_testn can_lfs_aix '!' can_lfs 0 "... with -D_LARGE_FILES=1" <lft.c test 1 = $HAVE_CAN_LFS_AIX || CPPFLAGS=$save_CPPFLAGS fi @@ -1136,17 +1270,17 @@ ac_testn sig_t <<-'EOF' #include <sys/types.h> #include <signal.h> #include <stddef.h> - int main(void) { return ((int)(ptrdiff_t)(sig_t)kill(0,0)); } + int main(void) { return ((int)(ptrdiff_t)(sig_t)(ptrdiff_t)kill(0,0)); } EOF ac_testn sighandler_t '!' sig_t 0 <<-'EOF' #include <sys/types.h> #include <signal.h> #include <stddef.h> - int main(void) { return ((int)(ptrdiff_t)(sighandler_t)kill(0,0)); } + int main(void) { return ((int)(ptrdiff_t)(sighandler_t)(ptrdiff_t)kill(0,0)); } EOF if test 1 = $HAVE_SIGHANDLER_T; then - CPPFLAGS="$CPPFLAGS -Dsig_t=sighandler_t" + add_cppflags -Dsig_t=sighandler_t HAVE_SIG_T=1 fi @@ -1154,14 +1288,14 @@ ac_testn __sighandler_t '!' sig_t 0 <<-'EOF' #include <sys/types.h> #include <signal.h> #include <stddef.h> - int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)kill(0,0)); } + int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)(ptrdiff_t)kill(0,0)); } EOF if test 1 = $HAVE___SIGHANDLER_T; then - CPPFLAGS="$CPPFLAGS -Dsig_t=__sighandler_t" + add_cppflags -Dsig_t=__sighandler_t HAVE_SIG_T=1 fi -test 1 = $HAVE_SIG_T || CPPFLAGS="$CPPFLAGS -Dsig_t=nosig_t" +test 1 = $HAVE_SIG_T || add_cppflags -Dsig_t=nosig_t ac_cppflags SIG_T # @@ -1179,9 +1313,10 @@ for what in name list; do extern const char *const _sys_sig${what}[]; int main(void) { return (_sys_sig${what}[0][0]); } EOF - if eval "test 1 = \$HAVE__SYS_SIG$uwhat"; then - CPPFLAGS="$CPPFLAGS -Dsys_sig$what=_sys_sig$what" - eval "HAVE_SYS_SIG$uwhat=1" + eval uwhat_v=\$HAVE__SYS_SIG$uwhat + if test 1 = "$uwhat_v"; then + add_cppflags -Dsys_sig$what=_sys_sig$what + eval HAVE_SYS_SIG$uwhat=1 fi ac_cppflags SYS_SIG$uwhat done @@ -1197,8 +1332,12 @@ EOF # ac_testn flock_ex '' 'flock and mmap' <<-'EOF' #include <sys/types.h> + #if HAVE_SYS_FILE_H #include <sys/file.h> + #endif + #if HAVE_SYS_MMAN_H #include <sys/mman.h> + #endif #include <fcntl.h> #include <stdlib.h> int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX), @@ -1264,18 +1403,30 @@ ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF' int main(void) { return ((int)(ptrdiff_t)(void *)nl_langinfo(CODESET)); } EOF -ac_test setmode mknod 1 <<-'EOF' - /* XXX imake style */ - /* XXX conditions correct? */ - #if defined(__MSVCRT__) || defined(__CYGWIN__) - /* force a failure: Win32 setmode() is not what we want... */ - int main(void) { return (thiswillneverbedefinedIhope()); } - #else +ac_test select <<-'EOF' #include <sys/types.h> - #include <unistd.h> - int main(int ac, char *av[]) { return (getmode(setmode(av[0]), - (mode_t)ac)); } + #include <sys/time.h> + #if HAVE_SYS_BSDTYPES_H + #include <sys/bsdtypes.h> #endif + #if HAVE_SYS_SELECT_H + #include <sys/select.h> + #endif + #if HAVE_BSTRING_H + #include <bstring.h> + #endif + #include <stddef.h> + #include <stdlib.h> + #include <string.h> + #if HAVE_STRINGS_H + #include <strings.h> + #endif + #include <unistd.h> + int main(void) { + struct timeval tv = { 1, 200000 }; + fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds); + return (select(FD_SETSIZE, &fds, NULL, NULL, &tv)); + } EOF ac_test setresugid <<-'EOF' @@ -1340,7 +1491,7 @@ CC=$save_CC; LDFLAGS=$save_LDFLAGS; LIBS=$save_LIBS # fd='if to use persistent history' ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1 -test 1 = $fv || check_categories=$check_categories,no-histfile +test 1 = $fv || check_categories="$check_categories no-histfile" ac_testdone ac_cppflags @@ -1365,7 +1516,7 @@ $e ... done. # the character count to standard output; cope for that echo wq >x ed x <x 2>/dev/null | grep 3 >/dev/null 2>&1 && \ - check_categories=$check_categories,$oldish_ed + check_categories="$check_categories $oldish_ed" rmf x vv.out if test 0 = $HAVE_SYS_SIGNAME; then @@ -1407,7 +1558,7 @@ mksh_cfg: NSIG' >conftest.c vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \ grep mksh_cfg: | \ sed 's/^mksh_cfg:[ ]*\([0-9x]*\).*$/\1:'$name/ - done | grep -v '^:' | while IFS=: read nr name; do + done | grep -v '^:' | sed 's/:/ /g' | while read nr name; do test $printf = echo || nr=`printf %d "$nr" 2>/dev/null` test $nr -gt 0 && test $nr -le $NSIG || continue case $sigseen in @@ -1422,11 +1573,9 @@ mksh_cfg: NSIG' >conftest.c $e done. fi -addsrcs '!' HAVE_SETMODE setmode.c addsrcs '!' HAVE_STRLCPY strlcpy.c addsrcs USE_PRINTF_BUILTIN printf.c -test 1 = "$USE_PRINTF_BUILTIN" && CPPFLAGS="$CPPFLAGS -DMKSH_PRINTF_BUILTIN" -test 0 = "$HAVE_SETMODE" && CPPFLAGS="$CPPFLAGS -DHAVE_CONFIG_H -DCONFIG_H_FILENAME=\\\"sh.h\\\"" +test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose" $e $bi$me: Finished configuration testing, now producing output.$ao @@ -1441,7 +1590,42 @@ esac cat >>test.sh <<-EOF LC_ALL=C PATH='$PATH'; export LC_ALL PATH test -n "\$KSH_VERSION" || exit 1 - check_categories=$check_categories + set -A check_categories -- $check_categories + pflag='$curdir/mksh' + sflag='$srcdir/check.t' + usee=0 Pflag=0 uset=0 vflag=0 xflag=0 + while getopts "C:e:Pp:s:t:v" ch; do case \$ch { + (C) check_categories[\${#check_categories[*]}]=\$OPTARG ;; + (e) usee=1; eflag=\$OPTARG ;; + (P) Pflag=1 ;; + (p) pflag=\$OPTARG ;; + (s) sflag=\$OPTARG ;; + (t) uset=1; tflag=\$OPTARG ;; + (v) vflag=1 ;; + (*) xflag=1 ;; + } + done + shift \$((OPTIND - 1)) + set -A args -- '$srcdir/check.pl' -p "\$pflag" -s "\$sflag" + x= + for y in "\${check_categories[@]}"; do + x=\$x,\$y + done + if [[ -n \$x ]]; then + args[\${#args[*]}]=-C + args[\${#args[*]}]=\${x#,} + fi + if (( usee )); then + args[\${#args[*]}]=-e + args[\${#args[*]}]=\$eflag + fi + (( Pflag )) && args[\${#args[*]}]=-P + if (( uset )); then + args[\${#args[*]}]=-t + args[\${#args[*]}]=\$tflag + fi + (( vflag )) && args[\${#args[*]}]=-v + (( xflag )) && args[\${#args[*]}]=-x # force usage by synerr print Testing mksh for conformance: fgrep MirOS: '$srcdir/check.t' fgrep MIRBSD '$srcdir/check.t' @@ -1451,14 +1635,13 @@ cat >>test.sh <<-EOF cstr="\$cstr"'print \$os . ", Perl version " . \$];' for perli in \$PERL perl5 perl no; do [[ \$perli = no ]] && exit 1 - perlos=\$(\$perli -e "\$cstr") 2>&- || continue + perlos=\$(\$perli -e "\$cstr") 2>/dev/null || continue print "Perl interpreter '\$perli' running on '\$perlos'" [[ -n \$perlos ]] && break done - exec \$perli '$srcdir/check.pl' -s '$srcdir/check.t' -p '$curdir/mksh' \${check_categories:+-C} \${check_categories#,} \$*$tsts + exec \$perli "\${args[@]}" "\$@"$tsts EOF chmod 755 test.sh -test $HAVE_CAN_COMBINE$cm = 0combine && cm=normal if test $cm = llvm; then emitbc="-emit-llvm -c" elif test $cm = dragonegg; then diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..03ca8bf --- /dev/null +++ b/src/Makefile @@ -0,0 +1,97 @@ +# $MirOS: src/bin/mksh/Makefile,v 1.88 2011/10/07 19:51:17 tg Exp $ +#- +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# Thorsten Glaser <tg@mirbsd.org> +# +# Provided that these terms and disclaimer and all copyright notices +# are retained or reproduced in an accompanying document, permission +# is granted to deal in this work without restriction, including un- +# limited rights to use, publicly perform, distribute, sell, modify, +# merge, give away, or sublicence. +# +# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to +# the utmost extent permitted by applicable law, neither express nor +# implied; without malicious intent or gross negligence. In no event +# may a licensor, author or contributor be held liable for indirect, +# direct, other damage, loss, or other issues arising in any way out +# of dealing in the work, even if advised of the possibility of such +# damage or existence of a defect, except proven that it results out +# of said person's immediate fault when using the work as intended. +#- +# use CPPFLAGS=-DDEBUG __CRAZY=Yes to check for certain more stuff + +.include <bsd.own.mk> + +PROG= mksh +SRCS= edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c \ + lalloc.c lex.c main.c misc.c shf.c syn.c tree.c var.c +.if !make(test-build) +CPPFLAGS+= -DMKSH_ASSUME_UTF8 \ + -DHAVE_ATTRIBUTE_BOUNDED=1 -DHAVE_ATTRIBUTE_FORMAT=1 \ + -DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \ + -DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \ + -DHAVE_SYS_BSDTYPES_H=0 -DHAVE_SYS_FILE_H=1 \ + -DHAVE_SYS_MKDEV_H=0 -DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_PARAM_H=1 \ + -DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SYSMACROS_H=0 \ + -DHAVE_BSTRING_H=0 -DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 \ + -DHAVE_LIBUTIL_H=0 -DHAVE_PATHS_H=1 -DHAVE_STDINT_H=1 \ + -DHAVE_STRINGS_H=1 -DHAVE_ULIMIT_H=0 -DHAVE_VALUES_H=0 \ + -DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \ + -DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 -DHAVE_RLIM_T=1 \ + -DHAVE_SIG_T=1 -DHAVE_SYS_SIGNAME=1 -DHAVE_SYS_SIGLIST=1 \ + -DHAVE_STRSIGNAL=0 -DHAVE_GETRUSAGE=1 -DHAVE_KILLPG=1 \ + -DHAVE_MKNOD=0 -DHAVE_MKSTEMP=1 -DHAVE_NICE=1 -DHAVE_REVOKE=1 \ + -DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \ + -DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 -DHAVE_SETGROUPS=1 \ + -DHAVE_STRCASESTR=1 -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 \ + -DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \ + -DHAVE_PERSISTENT_HISTORY=1 +COPTS+= -std=gnu99 -Wall +.endif + +USE_PRINTF_BUILTIN?= 0 +.if ${USE_PRINTF_BUILTIN} == 1 +.PATH: ${BSDSRCDIR}/usr.bin/printf +SRCS+= printf.c +CPPFLAGS+= -DMKSH_PRINTF_BUILTIN +.endif + +MANLINKS= [ false pwd sh sleep test true +BINLINKS= ${MANLINKS} echo domainname kill +.for _i in ${BINLINKS} +LINKS+= ${BINDIR}/${PROG} ${BINDIR}/${_i} +.endfor +.for _i in ${MANLINKS} +MLINKS+= ${PROG}.1 ${_i}.1 +.endfor + +regress: ${PROG} check.pl check.t + -rm -rf regress-dir + mkdir -p regress-dir + echo export FNORD=666 >regress-dir/.mkshrc + HOME=$$(realpath regress-dir) perl ${.CURDIR}/check.pl \ + -s ${.CURDIR}/check.t -v -p ./${PROG} + +test-build: .PHONY + -rm -rf build-dir + mkdir -p build-dir +.if ${USE_PRINTF_BUILTIN} == 1 + cp ${BSDSRCDIR}/usr.bin/printf/printf.c build-dir/ +.endif + cd build-dir; env CC=${CC:Q} CFLAGS=${CFLAGS:M*:Q} \ + CPPFLAGS=${CPPFLAGS:M*:Q} LDFLAGS=${LDFLAGS:M*:Q} \ + LIBS= NOWARN=-Wno-error TARGET_OS= CPP= /bin/sh \ + ${.CURDIR}/Build.sh -Q -r && ./test.sh -v + +cleandir: clean-extra + +clean-extra: .PHONY + -rm -rf build-dir regress-dir printf.o printf.ln + +distribution: + sed 's!\$$I''d\([:$$]\)!$$M''irSecuCron\1!g' \ + ${.CURDIR}/dot.mkshrc >${DESTDIR}/etc/skel/.mkshrc + chown ${BINOWN}:${CONFGRP} ${DESTDIR}/etc/skel/.mkshrc + chmod 0644 ${DESTDIR}/etc/skel/.mkshrc + +.include <bsd.prog.mk> diff --git a/src/check.pl b/src/check.pl index e793e95..7dfab36 100644 --- a/src/check.pl +++ b/src/check.pl @@ -1,7 +1,7 @@ -# $MirOS: src/bin/mksh/check.pl,v 1.23 2009/06/10 18:12:43 tg Rel $ +# $MirOS: src/bin/mksh/check.pl,v 1.27 2011/05/29 02:18:47 tg Exp $ # $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $ #- -# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 # Thorsten Glaser <tg@mirbsd.org> # # Provided that these terms and disclaimer and all copyright notices @@ -71,7 +71,8 @@ # environment. Programs are run with # the following minimal environment: # HOME, LD_LIBRARY_PATH, LOCPATH, -# LOGNAME, PATH, SHELL, USER +# LOGNAME, PATH, SHELL, UNIXMODE, +# USER # (values taken from the environment of # the test harness). # ENV is set to /nonexistant. @@ -135,6 +136,8 @@ # One category os:XXX is predefined # (XXX is the operating system name, # eg, linux, dec_osf). +# need-ctty 'yes' if the test needs a ctty, run +# with -C regress:no-ctty to disable. # Flag meanings: # r tag is required (eg, a test must have a name tag). # m value can be multiple lines. Lines must be prefixed with @@ -156,19 +159,19 @@ $os = defined $^O ? $^O : 'unknown'; ($prog = $0) =~ s#.*/##; $Usage = <<EOF ; -Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] name ... - -p p Use p as the program to test +Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-t tmo] name ... -C c Specify the comma separated list of categories the program belongs to (see category field). + -e e=v Set the environment variable e to v for all tests + (if no =v is given, the current value is used) + Only one -e option can be given at the moment, sadly. + -P program (-p) string has multiple words, and the program is in + the path (kludge option) + -p p Use p as the program to test -s s Read tests from file s; if s is a directory, it is recursively scaned for test files (which end in .t). -t t Use t as default time limit for tests (default is unlimited) - -P program (-p) string has multiple words, and the program is in - the path (kludge option) -v Verbose mode: print reason test failed. - -e e=v Set the environment variable e to v for all tests - (if no =v is given, the current value is used) - Only one -e option can be given at the moment, sadly. name specifies the name of the test(s) to run; if none are specified, all tests are run. EOF @@ -193,6 +196,8 @@ EOF 'expected-stderr', 'm', 'expected-stderr-pattern', 'm', 'category', 'm', + 'need-ctty', '', + 'need-pass', '', ); # Filled in by read_test() %internal_test_fields = ( @@ -213,13 +218,14 @@ $tempe = "/tmp/rte$$"; $tempdir = "/tmp/rtd$$"; $nfailed = 0; +$nifailed = 0; $nxfailed = 0; $npassed = 0; $nxpassed = 0; %known_tests = (); -if (!getopts('C:p:Ps:t:ve:')) { +if (!getopts('C:e:Pp:s:t:v')) { print STDERR $Usage; exit 1; } @@ -253,7 +259,7 @@ $all_tests = @ARGV == 0; # Set up a very minimal environment %new_env = (); foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME', - 'PATH', 'SHELL', 'USER')) { + 'PATH', 'SHELL', 'UNIXMODE', 'USER')) { $new_env{$env} = $ENV{$env} if defined $ENV{$env}; } $new_env{'ENV'} = '/nonexistant'; @@ -300,12 +306,13 @@ if (-d $test_set) { } &cleanup_exit() if !defined $ret; -$tot_failed = $nfailed + $nxfailed; +$tot_failed = $nfailed + $nifailed + $nxfailed; $tot_passed = $npassed + $nxpassed; if ($tot_failed || $tot_passed) { print "Total failed: $tot_failed"; + print " ($nifailed ignored)" if $nifailed; print " ($nxfailed unexpected)" if $nxfailed; - print " (as expected)" if $nfailed && !$nxfailed; + print " (as expected)" if $nfailed && !$nxfailed && !$nifailed; print "\nTotal passed: $tot_passed"; print " ($nxpassed unexpected)" if $nxpassed; print "\n"; @@ -319,7 +326,11 @@ cleanup_exit local($sig, $exitcode) = ('', 1); if ($_[0] eq 'ok') { - $exitcode = 0; + unless ($nxfailed) { + $exitcode = 0; + } else { + $exitcode = 1; + } } elsif ($_[0] ne '') { $sig = $_[0]; } @@ -616,8 +627,13 @@ run_test if ($failed) { if (!$test{'expected-fail'}) { - print "FAIL $name\n"; - $nxfailed++; + if ($test{'need-pass'}) { + print "FAIL $name\n"; + $nxfailed++; + } else { + print "FAIL $name (ignored)\n"; + $nifailed++; + } } else { print "fail $name (as expected)\n"; $nfailed++; @@ -642,6 +658,7 @@ category_check local(*test) = @_; local($c); + return 0 if ($test{'need-ctty'} && defined $categories{'regress:no-ctty'}); return 1 if (!defined $test{'category'}); local($ok) = 0; foreach $c (split(',', $test{'category'})) { @@ -1064,6 +1081,26 @@ read_test } else { $test{'expected-fail'} = 0; } + if (defined $test{'need-ctty'}) { + if ($test{'need-ctty'} !~ /^(yes|no)$/) { + print STDERR + "$prog:$test{':long-name'}: bad value for need-ctty field\n"; + return undef; + } + $test{'need-ctty'} = $1 eq 'yes'; + } else { + $test{'need-ctty'} = 0; + } + if (defined $test{'need-pass'}) { + if ($test{'need-pass'} !~ /^(yes|no)$/) { + print STDERR + "$prog:$test{':long-name'}: bad value for need-pass field\n"; + return undef; + } + $test{'need-pass'} = $1 eq 'yes'; + } else { + $test{'need-pass'} = 1; + } if (defined $test{'arguments'}) { local($firstc) = substr($test{'arguments'}, 0, 1); diff --git a/src/check.t b/src/check.t index c8e8caf..8df403a 100644 --- a/src/check.t +++ b/src/check.t @@ -1,9 +1,9 @@ -# $MirOS: src/bin/mksh/check.t,v 1.388 2010/08/24 15:47:44 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.483 2011/10/07 19:51:42 tg Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ #- -# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 # Thorsten Glaser <tg@mirbsd.org> # # Provided that these terms and disclaimer and all copyright notices @@ -25,7 +25,7 @@ # http://www.research.att.com/~gsf/public/ifs.sh expected-stdout: - @(#)MIRBSD KSH R39 2010/08/24 + @(#)MIRBSD KSH R40 2011/10/07 description: Check version of shell. stdin: @@ -65,6 +65,16 @@ category: disabled stdin: set --- +name: selftest-direct-builtin-call +description: + Check that direct builtin calls work +stdin: + ln -s "$__progname" cat + ln -s "$__progname" echo + ./echo -c 'echo foo' | ./cat -u +expected-stdout: + -c echo foo +--- name: alias-1 description: Check that recursion is detected/avoided in aliases. @@ -346,7 +356,7 @@ expected-stdout: --- name: bksl-nl-ign-1 description: - Check that \newline is not collasped after # + Check that \newline is not collapsed after # stdin: echo hi #there \ echo folks @@ -356,7 +366,7 @@ expected-stdout: --- name: bksl-nl-ign-2 description: - Check that \newline is not collasped inside single quotes + Check that \newline is not collapsed inside single quotes stdin: echo 'hi \ there' @@ -368,7 +378,7 @@ expected-stdout: --- name: bksl-nl-ign-3 description: - Check that \newline is not collasped inside single quotes + Check that \newline is not collapsed inside single quotes stdin: cat << \EOF hi \ @@ -418,7 +428,7 @@ expected-stdout: # name: bksl-nl-1 description: - Check that \newline is collasped before, in the middle of, and + Check that \newline is collapsed before, in the middle of, and after words stdin: \ @@ -430,7 +440,7 @@ expected-stdout: --- name: bksl-nl-2 description: - Check that \newline is collasped in $ sequences + Check that \newline is collapsed in $ sequences (ksh93 fails this) stdin: a=12 @@ -454,7 +464,7 @@ expected-stdout: --- name: bksl-nl-3 description: - Check that \newline is collasped in $(..) and `...` sequences + Check that \newline is collapsed in $(..) and `...` sequences (ksh93 fails this) stdin: echo $\ @@ -479,7 +489,7 @@ expected-stdout: --- name: bksl-nl-4 description: - Check that \newline is collasped in $((..)) sequences + Check that \newline is collapsed in $((..)) sequences (ksh93 fails this) stdin: echo $\ @@ -501,7 +511,7 @@ expected-stdout: --- name: bksl-nl-5 description: - Check that \newline is collasped in double quoted strings + Check that \newline is collapsed in double quoted strings stdin: echo "\ hi" @@ -516,7 +526,7 @@ expected-stdout: --- name: bksl-nl-6 description: - Check that \newline is collasped in here document delimiters + Check that \newline is collapsed in here document delimiters (ksh93 fails second part of this) stdin: a=12 @@ -539,7 +549,7 @@ expected-stdout: --- name: bksl-nl-7 description: - Check that \newline is collasped in double-quoted here-document + Check that \newline is collapsed in double-quoted here-document delimiter. stdin: a=12 @@ -558,7 +568,7 @@ expected-stdout: --- name: bksl-nl-8 description: - Check that \newline is collasped in various 2+ character tokens + Check that \newline is collapsed in various 2+ character tokens delimiter. (ksh93 fails this) stdin: @@ -995,6 +1005,45 @@ expected-stdout: 1 /bin 0 /tmp --- +name: cd-pe +description: + Check package for cd -Pe +need-pass: no +# the mv command fails on Cygwin +category: !os:cygwin +file-setup: file 644 "x" + mkdir noread noread/target noread/target/subdir + ln -s noread link + chmod 311 noread + cd -P$1 . + echo 0=$? + bwd=$PWD + cd -P$1 link/target + echo 1=$?,${PWD#$bwd/} + epwd=$($TSHELL -c pwd 2>/dev/null) + # This unexpectedly succeeds on GNU/Linux and MidnightBSD + #echo pwd=$?,$epwd + # expect: pwd=1, + mv ../../noread ../../renamed + cd -P$1 subdir + echo 2=$?,${PWD#$bwd/} + cd $bwd + chmod 755 renamed + rm -rf noread link renamed +stdin: + export TSHELL="$__progname" + "$__progname" x + echo "now with -e:" + "$__progname" x e +expected-stdout: + 0=0 + 1=0,noread/target + 2=0,noread/target/subdir + now with -e: + 0=0 + 1=0,noread/target + 2=1,noread/target/subdir +--- name: env-prompt description: Check that prompt not printed when processing ENV @@ -1003,6 +1052,7 @@ file-setup: file 644 "foo" XXX=_ PS1=X false && echo hmmm +need-ctty: yes arguments: !-i! stdin: echo hi${XXX}there @@ -1360,6 +1410,56 @@ expected-stdout: 9 EQAL brac foo x c x} baz 9 QSTN brac foo x c x} baz --- +name: expand-threecolons-dblq +description: + Check for a particular thing that used to segfault +stdin: + TEST=1234 + echo "${TEST:1:2:3}" + echo $? but still living +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: expand-threecolons-unq +description: + Check for a particular thing that used to not error out +stdin: + TEST=1234 + echo ${TEST:1:2:3} + echo $? but still living +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: expand-weird-1 +description: + Check corner case of trim expansion vs. $# vs. ${#var} +stdin: + set 1 2 3 4 5 6 7 8 9 10 11 + echo ${#} # value of $# + echo ${##} # length of $# + echo ${##1} # $# trimmed 1 + set 1 2 3 4 5 6 7 8 9 10 11 12 + echo ${##1} +expected-stdout: + 11 + 2 + 1 + 2 +--- +name: expand-weird-2 +description: + Check corner case of ${var?} vs. ${#var} +stdin: + (exit 0) + echo $? = ${#?} . + (exit 111) + echo $? = ${#?} . +expected-stdout: + 0 = 1 . + 111 = 3 . +--- name: eglob-bad-1 description: Check that globbing isn't done when glob has syntax error @@ -1495,6 +1595,27 @@ expected-stdout: 3: abcdef 4: cdef --- +name: eglob-trim-3 +description: + Check eglobbing works in trims, for Korn Shell + Ensure eglobbing does not work for reduced-feature /bin/sh +stdin: + set +o sh + x=foobar + y=foobaz + z=fooba\? + echo "<${x%bar|baz},${y%bar|baz},${z%\?}>" + echo "<${x%ba(r|z)},${y%ba(r|z)}>" + set -o sh + echo "<${x%bar|baz},${y%bar|baz},${z%\?}>" + z='foo(bar' + echo "<${z%(*}>" +expected-stdout: + <foo,foo,fooba> + <foo,foo> + <foobar,foobaz,fooba> + <foo> +--- name: eglob-substrpl-1 description: Check eglobbing works in substs... and they work at all @@ -1742,6 +1863,8 @@ expected-stdout: name: glob-bad-2 description: Check that symbolic links aren't stat()'d +# breaks on FreeMiNT (cannot unlink dangling symlinks) +category: !os:mint file-setup: dir 755 "dir" file-setup: symlink 644 "dir/abc" non-existent-file @@ -1787,7 +1910,8 @@ name: glob-range-3 description: Check that globbing matches the right things... # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition) -category: !os:darwin +# breaks on Cygwin 1.7 (files are now UTF-16 or something) +category: !os:cygwin,!os:darwin file-setup: file 644 "ac" stdin: echo a[-]* @@ -2007,6 +2131,230 @@ stdin: expected-stdout: one --- +name: heredoc-9e +description: + Check here string related regression with multiple iops +stdin: + echo $(tr r z <<<'bar' 2>&-) +expected-stdout: + baz +--- +name: heredoc-10 +description: + Check direct here document assignment +stdin: + x=u + va=<<EOF + =a $x \x40= + EOF + vb=<<'EOF' + =b $x \x40= + EOF + function foo { + vc=<<-EOF + =c $x \x40= + EOF + } + typeset -f foo + foo + # rather nonsensical, but… + vd=<<<"=d $x \x40=" + ve=<<<'=e $x \x40=' + vf=<<<$'=f $x \x40=' + # now check + print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |" +expected-stdout: + function foo { + vc= <<-EOF + =c $x \x40= + EOF + + } + | va={=a u \x40= + } vb={=b $x \x40= + } vc={=c u \x40= + } vd={=d u \x40= + } ve={=e $x \x40= + } vf={=f $x @= + } | +--- +name: heredoc-11 +description: + Check here documents with no or empty delimiter +stdin: + x=u + va=<< + =a $x \x40= + << + vb=<<'' + =b $x \x40= + + function foo { + vc=<<- + =c $x \x40= + << + vd=<<-'' + =d $x \x40= + + } + typeset -f foo + foo + print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |" +expected-stdout: + function foo { + vc= <<- + =c $x \x40= + << + + vd= <<-"" + =d $x \x40= + + + } + | va={=a u \x40= + } vb={=b $x \x40= + } vc={=c u \x40= + } vd={=d $x \x40= + } | +--- +name: heredoc-comsub-1 +description: + Tests for here documents in COMSUB, taken from Austin ML +stdin: + text=$(cat <<EOF + here is the text + EOF) + echo = $text = +expected-stdout: + = here is the text = +--- +name: heredoc-comsub-2 +description: + Tests for here documents in COMSUB, taken from Austin ML +stdin: + unbalanced=$(cat <<EOF + this paren ) is a problem + EOF) + echo = $unbalanced = +expected-stdout: + = this paren ) is a problem = +--- +name: heredoc-comsub-3 +description: + Tests for here documents in COMSUB, taken from Austin ML +stdin: + balanced=$(cat <<EOF + these parens ( ) are not a problem + EOF) + echo = $balanced = +expected-stdout: + = these parens ( ) are not a problem = +--- +name: heredoc-comsub-4 +description: + Tests for here documents in COMSUB, taken from Austin ML +stdin: + balanced=$(cat <<EOF + these parens \( ) are a problem + EOF) + echo = $balanced = +expected-stdout: + = these parens \( ) are a problem = +--- +name: heredoc-subshell-1 +description: + Tests for here documents in subshells, taken from Austin ML +stdin: + (cat <<EOF + some text + EOF) + echo end +expected-stdout: + some text + end +--- +name: heredoc-subshell-2 +description: + Tests for here documents in subshells, taken from Austin ML +stdin: + (cat <<EOF + some text + EOF + ) + echo end +expected-stdout: + some text + end +--- +name: heredoc-subshell-3 +description: + Tests for here documents in subshells, taken from Austin ML +stdin: + (cat <<EOF; ) + some text + EOF + echo end +expected-stdout: + some text + end +--- +name: heredoc-weird-1 +description: + Tests for here documents, taken from Austin ML + Documents current state in mksh, *NOT* necessarily correct! +stdin: + cat <<END + hello + END\ + END + END + echo end +expected-stdout: + hello + ENDEND + end +--- +name: heredoc-weird-2 +description: + Tests for here documents, taken from Austin ML +stdin: + cat <<' END ' + hello + END + echo end +expected-stdout: + hello + end +--- +name: heredoc-weird-4 +description: + Tests for here documents, taken from Austin ML + Documents current state in mksh, *NOT* necessarily correct! +stdin: + cat <<END + hello\ + END + END + echo end +expected-stdout: + helloEND + end +--- +name: heredoc-weird-5 +description: + Tests for here documents, taken from Austin ML + Documents current state in mksh, *NOT* necessarily correct! +stdin: + cat <<END + hello + \END + END + echo end +expected-stdout: + hello + \END + end +--- name: heredoc-quoting-unsubst description: Check for correct handling of quoted characters in @@ -2188,6 +2536,7 @@ description: late. Heredoc in function, backgrounded call to function. This check can fail on slow machines (<100 MHz), or Cygwin, that's normal. +need-pass: no stdin: TMPDIR=$PWD # Background eval so main shell doesn't do parsing @@ -2215,6 +2564,7 @@ expected-stdout: name: history-basic description: See if we can test history at all +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2231,6 +2581,7 @@ expected-stderr-pattern: name: history-dups description: Verify duplicates and spaces are not entered +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2251,6 +2602,7 @@ expected-stderr-pattern: name: history-unlink description: Check if broken HISTFILEs do not cause trouble +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=foo/hist.file! file-setup: file 644 "Env" @@ -2268,11 +2620,12 @@ expected-stdout: hi 1 echo hi expected-stderr-pattern: - /(.*cannot unlink HISTFILE.*\n)?X*$/ + /(.*can't unlink HISTFILE.*\n)?X*$/ --- name: history-e-minus-1 description: Check if more recent command is executed +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2292,6 +2645,7 @@ name: history-e-minus-2 description: Check that repeated command is printed before command is re-executed. +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2311,6 +2665,7 @@ description: fc -e - fails when there is no history (ksh93 has a bug that causes this to fail) (ksh88 loops on this) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2326,6 +2681,7 @@ expected-stderr-pattern: name: history-e-minus-4 description: Check if "fc -e -" command output goes to stdout. +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2344,6 +2700,7 @@ expected-stderr-pattern: name: history-e-minus-5 description: fc is replaced in history by new command. +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2369,6 +2726,7 @@ name: history-list-1 description: List lists correct range (ksh88 fails 'cause it lists the fc command) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2392,6 +2750,7 @@ description: Lists oldest history if given pre-historic number (ksh93 has a bug that causes this to fail) (ksh88 fails 'cause it lists the fc command) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2414,6 +2773,7 @@ expected-stderr-pattern: name: history-list-3 description: Can give number 'options' to fc +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2437,6 +2797,7 @@ expected-stderr-pattern: name: history-list-4 description: -1 refers to previous command +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2459,6 +2820,7 @@ expected-stderr-pattern: name: history-list-5 description: List command stays in history +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2485,6 +2847,7 @@ name: history-list-6 description: HISTSIZE limits about of history kept. (ksh88 fails 'cause it lists the fc command) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! file-setup: file 644 "Env" @@ -2510,6 +2873,7 @@ expected-stderr-pattern: name: history-list-7 description: fc allows too old/new errors in range specification +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! file-setup: file 644 "Env" @@ -2536,6 +2900,7 @@ expected-stderr-pattern: name: history-list-r-1 description: test -r flag in history +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2562,6 +2927,7 @@ expected-stderr-pattern: name: history-list-r-2 description: If first is newer than last, -r is implied. +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2588,6 +2954,7 @@ expected-stderr-pattern: name: history-list-r-3 description: If first is newer than last, -r is cancelled. +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2614,6 +2981,7 @@ expected-stderr-pattern: name: history-subst-1 description: Basic substitution +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2632,6 +3000,7 @@ expected-stderr-pattern: name: history-subst-2 description: Does subst find previous command? +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2650,6 +3019,7 @@ expected-stderr-pattern: name: history-subst-3 description: Does subst find previous command when no arguments given +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2669,6 +3039,7 @@ name: history-subst-4 description: Global substitutions work (ksh88 and ksh93 do not have -g option) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2686,6 +3057,7 @@ name: history-subst-5 description: Make sure searches don't find current (fc) command (ksh88/ksh93 don't have the ? prefix thing so they fail this test) +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2707,6 +3079,8 @@ description: that prints no prompts). This is for oldish ed(1) which write the character count to stdout. category: stdout-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2729,6 +3103,8 @@ name: history-ed-2-old description: Correct command is edited when number given category: stdout-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2760,6 +3136,8 @@ description: (NOTE: adjusted for COMPLEX HISTORY compile time option) (ksh88 fails 'cause it lists the fc command) category: stdout-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2791,6 +3169,8 @@ description: Basic (ed) editing works (assumes you have generic ed editor that prints no prompts). This is for newish ed(1) and stderr. category: !no-stderr-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2811,6 +3191,8 @@ name: history-ed-2 description: Correct command is edited when number given category: !no-stderr-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2838,6 +3220,8 @@ description: Newly created multi line commands show up as single command in history. category: !no-stderr-ed +need-ctty: yes +need-pass: no arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -2943,23 +3327,6 @@ stdin: expected-stdout: <:> --- -name: IFS-space-colon-3 -description: - Simple test, IFS=<white-space>: - pdksh fails both of these tests - not sure whether #2 is correct -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - x= - set -- - showargs "$x$@" 1 - showargs "$@$x" 2 -expected-fail: yes -expected-stdout: - <> <1> - <> <2> ---- name: IFS-space-colon-4 description: Simple test, IFS=<white-space>: @@ -3051,6 +3418,7 @@ description: Syntax errors in expressions and effects on bases (interactive so errors don't cause exits) (ksh88 fails this test - shell exits, even with -i) +need-ctty: yes arguments: !-i! stdin: PS1= # minimise prompt hassles @@ -3346,6 +3714,26 @@ expected-stdout: line <6> expected-exit: 1 --- +name: unknown-trap +description: + Ensure unknown traps are not a syntax error +stdin: + ( + trap "echo trap 1 executed" UNKNOWNSIGNAL || echo "foo" + echo =1 + trap "echo trap 2 executed" UNKNOWNSIGNAL EXIT 999999 FNORD + echo = $? + ) 2>&1 | sed "s^${__progname}: <stdin>\[[0-9]*]PROG" +expected-stdout: + PROG: trap: bad signal 'UNKNOWNSIGNAL' + foo + =1 + PROG: trap: bad signal 'UNKNOWNSIGNAL' + PROG: trap: bad signal '999999' + PROG: trap: bad signal 'FNORD' + = 3 + trap 2 executed +--- name: read-IFS-1 description: Simple test, default IFS @@ -3372,6 +3760,74 @@ stdin: expected-stdout: [abc] --- +name: read-regress-1 +description: + Check a regression of read +file-setup: file 644 "foo" + foo bar + baz + blah +stdin: + while read a b c; do + read d + break + done <foo + echo "<$a|$b|$c><$d>" +expected-stdout: + <foo|bar|><baz> +--- +name: read-delim-1 +description: + Check read with delimiters +stdin: + emit() { + printf 'foo bar\tbaz\nblah \0blub\tblech\nmyok meck \0' + } + emit | while IFS= read -d "" foo; do print -r -- "<$foo>"; done + emit | while read -d "" foo; do print -r -- "<$foo>"; done + emit | while read -d "eh?" foo; do print -r -- "<$foo>"; done +expected-stdout: + <foo bar baz + blah > + <blub blech + myok meck > + <foo bar baz + blah> + <blub blech + myok meck> + <foo bar baz + blah blub bl> + <ch + myok m> +--- +name: read-ext-1 +description: + Check read with number of bytes specified, and -A +stdin: + print 'foo\nbar' >x1 + print -n x >x2 + print 'foo\\ bar baz' >x3 + x1a=u; read x1a <x1 + x1b=u; read -N-1 x1b <x1 + x2a=u; read x2a <x2; r2a=$? + x2b=u; read -N2 x2c <x2; r2b=$? + x2c=u; read -n2 x2c <x2; r2c=$? + x3a=u; read -A x3a <x3 + print -r "x1a=<$x1a>" + print -r "x1b=<$x1b>" + print -r "x2a=$r2a<$x2a>" + print -r "x2b=$r2b<$x2b>" + print -r "x2c=$r2c<$x2c>" + print -r "x3a=<${x3a[0]}|${x3a[1]}|${x3a[2]}>" +expected-stdout: + x1a=<foo> + x1b=<foo + bar> + x2a=1<x> + x2b=1<u> + x2c=0<x> + x3a=<foo bar|baz|> +--- name: regression-1 description: Lex array code had problems with this. @@ -3782,6 +4238,9 @@ name: regression-33 description: Does umask print a leading 0 when umask is 3 digits? stdin: + # on MiNT, the first umask call seems to fail + umask 022 + # now, the test proper umask 222 umask expected-stdout: @@ -4006,6 +4465,7 @@ file-setup: file 644 "env" PS1=Y PS2=X env-setup: !ENV=./env! +need-ctty: yes arguments: !-i! stdin: alias foo='echo hi ; ' @@ -4036,6 +4496,7 @@ file-setup: file 644 "env" file-setup: file 644 "abc" stuff env-setup: !ENV=./env! +need-ctty: yes arguments: !-i! stdin: sed 's/^/X /' < ab* @@ -4369,6 +4830,78 @@ stdin: time } --- +name: regression-65 +description: + check for a regression with sleep builtin and signal mask +category: !nojsig +time-limit: 3 +stdin: + sleep 1 + echo blub |& + while read -p line; do :; done + echo ok +expected-stdout: + ok +--- +name: readonly-0 +description: + Ensure readonly is honoured for assignments and unset +stdin: + "$__progname" -c 'u=x; echo $? $u .' || echo aborted, $? + echo = + "$__progname" -c 'readonly u; u=x; echo $? $u .' || echo aborted, $? + echo = + "$__progname" -c 'u=x; readonly u; unset u; echo $? $u .' || echo aborted, $? +expected-stdout: + 0 x . + = + aborted, 2 + = + 1 x . +expected-stderr-pattern: + /read *only/ +--- +name: readonly-1 +description: + http://austingroupbugs.net/view.php?id=367 for export +stdin: + "$__progname" -c 'readonly foo; export foo=a; echo $?' || echo aborted, $? +expected-stdout: + aborted, 2 +expected-stderr-pattern: + /read *only/ +--- +name: readonly-2a +description: + Check that getopts works as intended, for readonly-2b to be valid +stdin: + "$__progname" -c 'set -- -a b; getopts a c; echo $? $c .; getopts a c; echo $? $c .' || echo aborted, $? +expected-stdout: + 0 a . + 1 ? . +--- +name: readonly-2b +description: + http://austingroupbugs.net/view.php?id=367 for getopts +stdin: + "$__progname" -c 'readonly c; set -- -a b; getopts a c; echo $? $c .' || echo aborted, $? +expected-stdout: + 2 . +expected-stderr-pattern: + /read *only/ +--- +name: readonly-3 +description: + http://austingroupbugs.net/view.php?id=367 for read +stdin: + echo x | "$__progname" -c 'read s; echo $? $s .' || echo aborted, $? + echo y | "$__progname" -c 'readonly s; read s; echo $? $s .' || echo aborted, $? +expected-stdout: + 0 x . + 2 . +expected-stderr-pattern: + /read *only/ +--- name: syntax-1 description: Check that lone ampersand is a syntax error @@ -4567,6 +5100,7 @@ expected-stdout: name: xxx-exec-1 description: Check that exec exits for built-ins +need-ctty: yes arguments: !-i! stdin: exec echo hi @@ -4614,6 +5148,7 @@ expected-stdout: name: xxx-status-1 description: Check that blank lines don't clear $? +need-ctty: yes arguments: !-i! stdin: (exit 1) @@ -4702,12 +5237,12 @@ description: Check some "exit on error" conditions stdin: set -ex - /usr/bin/env false && echo something + env false && echo something echo END expected-stdout: END expected-stderr: - + /usr/bin/env false + + env false + echo END --- name: exit-err-2 @@ -4715,15 +5250,15 @@ description: Check some "exit on error" edge conditions (POSIXly) stdin: set -ex - if /usr/bin/env true; then - /usr/bin/env false && echo something + if env true; then + env false && echo something fi echo END expected-stdout: END expected-stderr: - + /usr/bin/env true - + /usr/bin/env false + + env true + + env false + echo END --- name: exit-err-3 @@ -4845,6 +5380,16 @@ expected-stdout: E 0 F 0 --- +name: exit-trap-1 +description: + Check that "exit" with no arguments behaves SUSv4 conformant. +stdin: + trap 'echo hi; exit' EXIT + exit 9 +expected-stdout: + hi +expected-exit: 9 +--- name: test-stlt-1 description: Check that test also can handle string1 < string2 etc. @@ -4981,6 +5526,7 @@ description: Part 2: verify mkshrc can be read (interactive shells) file-setup: file 644 ".mkshrc" FNORD=42 +need-ctty: yes arguments: !-i! env-setup: !HOME=.!ENV=!PS1=! stdin: @@ -5104,6 +5650,7 @@ expected-stdout: name: pipeline-2 description: check that co-processes work with TCOMs, TPIPEs and TPARENs +category: !nojsig stdin: "$__progname" -c 'i=100; echo hi |& while read -p line; do echo "$((i++)) $line"; done' "$__progname" -c 'i=200; echo hi | cat |& while read -p line; do echo "$((i++)) $line"; done' @@ -5113,10 +5660,33 @@ expected-stdout: 200 hi 300 hi --- +name: pipeline-3 +description: + Check that PIPESTATUS does what it's supposed to +stdin: + echo 1 $PIPESTATUS . + echo 2 ${PIPESTATUS[0]} . + echo 3 ${PIPESTATUS[1]} . + (echo x; exit 12) | (cat; exit 23) | (cat; exit 42) + echo 5 $? , $PIPESTATUS , ${PIPESTATUS[0]} , ${PIPESTATUS[1]} , ${PIPESTATUS[2]} , ${PIPESTATUS[3]} . + echo 6 ${PIPESTATUS[0]} . + set | fgrep PIPESTATUS + echo 8 $(set | fgrep PIPESTATUS) . +expected-stdout: + 1 0 . + 2 0 . + 3 . + x + 5 42 , 12 , 12 , 23 , 42 , . + 6 0 . + PIPESTATUS[0]=0 + 8 PIPESTATUS[0]=0 PIPESTATUS[1]=0 . +--- name: persist-history-1 description: Check if persistent history saving works category: !no-histfile +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! file-setup: file 644 "Env" @@ -5128,6 +5698,40 @@ expected-stdout-pattern: expected-stderr-pattern: /^X*$/ --- +name: typeset-1 +description: + Check that global does what typeset is supposed to do +stdin: + set -A arrfoo 65 + foo() { + global -Uui16 arrfoo[*] + } + echo before ${arrfoo[0]} . + foo + echo after ${arrfoo[0]} . + set -A arrbar 65 + bar() { + echo inside before ${arrbar[0]} . + arrbar[0]=97 + echo inside changed ${arrbar[0]} . + global -Uui16 arrbar[*] + echo inside typeset ${arrbar[0]} . + arrbar[0]=48 + echo inside changed ${arrbar[0]} . + } + echo before ${arrbar[0]} . + bar + echo after ${arrbar[0]} . +expected-stdout: + before 65 . + after 16#41 . + before 65 . + inside before 65 . + inside changed 97 . + inside typeset 16#61 . + inside changed 16#30 . + after 16#30 . +--- name: typeset-padding-1 description: Check if left/right justification works as per TFM @@ -5208,7 +5812,7 @@ expected-stdout: mit ohne = - : mit + : ohne --- name: utf8bom-2 description: @@ -5216,6 +5820,7 @@ description: XXX if the OS can already execute them, we lose note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text +need-pass: no category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh env-setup: !FOO=BAR! stdin: @@ -5239,12 +5844,17 @@ expected-stderr-pattern: name: utf8bom-3 description: Reading the UTF-8 BOM should enable the utf8-mode flag + (temporarily for COMSUBs) stdin: "$__progname" -c ':; if [[ $- = *U* ]]; then echo 1 on; else echo 1 off; fi' "$__progname" -c ':; if [[ $- = *U* ]]; then echo 2 on; else echo 2 off; fi' + "$__progname" -c 'if [[ $- = *U* ]]; then echo 3 on; else echo 3 off; fi; x=$(:; if [[ $- = *U* ]]; then echo 4 on; else echo 4 off; fi); echo $x; if [[ $- = *U* ]]; then echo 5 on; else echo 5 off; fi' expected-stdout: 1 off 2 on + 3 off + 4 on + 5 off --- name: utf8opt-1a description: @@ -5281,7 +5891,9 @@ description: -DMKSH_ASSUME_UTF8=1 => not expected, please investigate -UMKSH_ASSUME_UTF8 => not expected, but if your OS is old, try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh +need-pass: no category: !os:hpux +need-ctty: yes arguments: !-i! env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8! stdin: @@ -5300,6 +5912,7 @@ description: Check that the utf8-mode flag is set at interactive startup Expected failure if -DMKSH_ASSUME_UTF8=0 category: os:hpux +need-ctty: yes arguments: !-i! env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8! stdin: @@ -5313,29 +5926,40 @@ expected-stdout: expected-stderr-pattern: /(# )*/ --- -name: utf8opt-3 +name: utf8opt-3a description: Ensure ±U on the command line is honoured - (this test may pass falsely depending on CPPFLAGS) + (these two tests may pass falsely depending on CPPFLAGS) stdin: export i=0 code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi' let i++; "$__progname" -U -c "$code" let i++; "$__progname" +U -c "$code" + echo $((++i)) done +expected-stdout: + 1 on + 2 off + 3 done +--- +name: utf8opt-3b +description: + Ensure ±U on the command line is honoured, interactive shells +need-ctty: yes +stdin: + export i=0 + code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi' let i++; "$__progname" -U -ic "$code" let i++; "$__progname" +U -ic "$code" echo $((++i)) done expected-stdout: 1 on 2 off - 3 on - 4 off - 5 done + 3 done --- name: aliases-1 description: Check if built-in shell aliases are okay -category: !arge +category: !android,!arge stdin: alias typeset -f @@ -5351,13 +5975,14 @@ expected-stdout: nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' + stop='kill -STOP' suspend='kill -STOP $$' type='whence -v' --- name: aliases-1-hartz4 description: Check if built-in shell aliases are okay -category: arge +category: android,arge stdin: alias typeset -f @@ -5391,7 +6016,6 @@ name: aliases-3a description: Check if running as sh disables built-in aliases (except a few) category: disabled -arguments: !-o!sh! stdin: cp "$__progname" sh ./sh -c 'alias; typeset -f' @@ -5403,7 +6027,7 @@ expected-stdout: name: aliases-2b description: Check if “set -o sh” does not influence built-in aliases -category: !arge +category: !android,!arge arguments: !-o!sh! stdin: alias @@ -5420,14 +6044,14 @@ expected-stdout: nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' + stop='kill -STOP' suspend='kill -STOP $$' type='whence -v' --- name: aliases-3b description: Check if running as sh does not influence built-in aliases -category: !arge -arguments: !-o!sh! +category: !android,!arge stdin: cp "$__progname" sh ./sh -c 'alias; typeset -f' @@ -5444,13 +6068,14 @@ expected-stdout: nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' + stop='kill -STOP' suspend='kill -STOP $$' type='whence -v' --- name: aliases-2b-hartz4 description: Check if “set -o sh” does not influence built-in aliases -category: arge +category: android,arge arguments: !-o!sh! stdin: alias @@ -5472,8 +6097,7 @@ expected-stdout: name: aliases-3b-hartz4 description: Check if running as sh does not influence built-in aliases -category: arge -arguments: !-o!sh! +category: android,arge stdin: cp "$__progname" sh ./sh -c 'alias; typeset -f' @@ -5528,6 +6152,17 @@ stdin: expected-stdout: makro --- +name: aliases-funcdef-4 +description: + Functions should only take over if actually being defined +stdin: + alias local + :|| local() { :; } + alias local +expected-stdout: + local=typeset + local=typeset +--- name: arrays-1 description: Check if Korn Shell arrays work as expected @@ -5538,7 +6173,7 @@ stdin: expected-stdout: 5|a|$v|c d|$v|b| --- -name: arrays-2 +name: arrays-2a description: Check if bash-style arrays work as expected category: !smksh @@ -5549,6 +6184,33 @@ stdin: expected-stdout: 5|a|$v|c d|$v|b| --- +name: arrays-2b +description: + Check if bash-style arrays work as expected, with newlines +category: !smksh +stdin: + test -n "$ZSH_VERSION" && setopt KSH_ARRAYS + v="e f" + foo=(a + bc + d \$v "$v" '$v' g + ) + printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo + foo=(a\ + bc + d \$v "$v" '$v' g + ) + printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo + foo=(a\ + bc\\ + d \$v "$v" '$v' + g) + printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo +expected-stdout: + 7|a|bc|d|$v|e f|$v|g| + 7|a|bc|d|$v|e f|$v|g| + 6|abc\|d|$v|e f|$v|g|| +--- name: arrays-3 description: Check if array bounds are uint32_t @@ -5743,6 +6405,29 @@ expected-stdout: 2g 009 . 2h 00001 00002 . --- +name: arrays-9a +description: + Check that we can concatenate arrays +category: !smksh +stdin: + unset foo; foo=(bar); foo+=(baz); echo 1 ${!foo[*]} : ${foo[*]} . + unset foo; foo=(foo bar); foo+=(baz); echo 2 ${!foo[*]} : ${foo[*]} . + unset foo; foo=([2]=foo [0]=bar); foo+=(baz [5]=quux); echo 3 ${!foo[*]} : ${foo[*]} . +expected-stdout: + 1 0 1 : bar baz . + 2 0 1 2 : foo bar baz . + 3 0 2 3 5 : bar foo baz quux . +--- +name: arrays-9b +description: + Check that we can concatenate parameters too +stdin: + unset foo; foo=bar; foo+=baz; echo 1 $foo . + unset foo; typeset -i16 foo=10; foo+=20; echo 2 $foo . +expected-stdout: + 1 barbaz . + 2 16#a20 . +--- name: varexpand-substr-1 description: Check if bash-style substring expansion works @@ -5865,6 +6550,17 @@ expected-stdout: c we d we --- +name: varexpand-special-hash +description: + Check special ${var@x} expansion for x=hash +stdin: + typeset -i8 foo=10 + bar=baz + unset baz + print ${foo@#} ${bar@#} ${baz@#} . +expected-stdout: + D50219A0 20E5DB5B 00000001 . +--- name: varexpand-null-1 description: Ensure empty strings expand emptily @@ -5924,8 +6620,9 @@ stdin: '\U\V\W\X\Y\Z\[\\\]\^\_\`\a\b \d\e\f\g\h\i\j\k\l\m\n\o\p' \ '\q\r\s\t\u\v\w\x\y\z\{\|\}\~' '\u20acd' '\U20acd' '\x123' \ '\0x' '\0123' '\01234' | { + # integer-base-one-3As typeset -Uui16 -Z11 pos=0 - typeset -Uui16 -Z5 hv + typeset -Uui16 -Z5 hv=2147483647 typeset -i1 wc=0x0A dasc= nl=${wc#1#} @@ -5948,13 +6645,11 @@ stdin: line=${line:1} done done - if (( (pos & 15) != 1 )); then - while (( pos & 15 )); do - print -n ' ' - (( (pos++ & 15) == 7 )) && print -n -- '- ' - done - print "$dasc|" - fi + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" } expected-stdout: 00000000 5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27 |\ \!\"\#\$\%\&\'| @@ -5971,6 +6666,22 @@ expected-stdout: 000000B0 E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20 |...d ... .3 x S | 000000C0 53 34 0A - |S4.| --- +name: dollar-doublequoted-strings +description: + Check that a $ preceding "…" is ignored +stdin: + echo $"Localise me!" + cat <<<$"Me too!" + V=X + aol=aol + cat <<-$"aol" + I do not take a $V for a V! + aol +expected-stdout: + Localise me! + Me too! + I do not take a $V for a V! +--- name: dollar-quoted-strings description: Check backslash expansion by $'…' strings @@ -5982,8 +6693,9 @@ stdin: $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \ $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\ b' | { + # integer-base-one-3As typeset -Uui16 -Z11 pos=0 - typeset -Uui16 -Z5 hv + typeset -Uui16 -Z5 hv=2147483647 typeset -i1 wc=0x0A dasc= nl=${wc#1#} @@ -6006,13 +6718,11 @@ stdin: line=${line:1} done done - if (( (pos & 15) != 1 )); then - while (( pos & 15 )); do - print -n ' ' - (( (pos++ & 15) == 7 )) && print -n -- '- ' - done - print "$dasc|" - fi + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" } expected-stdout: 00000000 20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F | !"#$%&'()*+,-./| @@ -6249,9 +6959,10 @@ expected-stderr-pattern: /1#: unexpected ''/ expected-exit: e != 0 --- -name: integer-base-one-3A +name: integer-base-one-3As description: some sample code for hexdumping + not NUL safe; input lines must be NL terminated stdin: { print 'Hello, World!\\\nこんにちは!' @@ -6261,10 +6972,11 @@ stdin: while (( i++ < 0x1FF )); do print -n "\x${i#16#1}" done - print + print '\0z' } | { + # integer-base-one-3As typeset -Uui16 -Z11 pos=0 - typeset -Uui16 -Z5 hv + typeset -Uui16 -Z5 hv=2147483647 typeset -i1 wc=0x0A dasc= nl=${wc#1#} @@ -6287,13 +6999,11 @@ stdin: line=${line:1} done done - if (( (pos & 15) != 1 )); then - while (( pos & 15 )); do - print -n ' ' - (( (pos++ & 15) == 7 )) && print -n -- '- ' - done - print "$dasc|" - fi + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" } expected-stdout: 00000000 48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3 |Hello, World!\..| @@ -6314,11 +7024,12 @@ expected-stdout: 000000F0 CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE |................| 00000100 DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE |................| 00000110 EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE |................| - 00000120 FF 0A - |..| + 00000120 FF 7A 0A - |.z.| --- -name: integer-base-one-3W +name: integer-base-one-3Ws description: some sample code for hexdumping Unicode + not NUL safe; input lines must be NL terminated stdin: set -U { @@ -6336,7 +7047,9 @@ stdin: print \\xc0\\x80 # non-minimalistic print \\xe0\\x80\\x80 # non-minimalistic print '�' # end of range + print '\0z' # embedded NUL } | { + # integer-base-one-3Ws typeset -Uui16 -Z11 pos=0 typeset -Uui16 -Z7 hv typeset -i1 wc=0x0A @@ -6371,13 +7084,174 @@ stdin: dasc=$dasc$dch done done - if (( pos & 7 )); then - while (( pos & 7 )); do - print -n ' ' - (( (pos++ & 7) == 3 )) && print -n -- '- ' + while (( pos & 7 )); do + print -n ' ' + (( (pos++ & 7) == 3 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" + } +expected-stdout: + 00000000 0048 0065 006C 006C - 006F 002C 0020 0057 |Hello, W| + 00000008 006F 0072 006C 0064 - 0021 005C 000A 3053 |orld!\.こ| + 00000010 3093 306B 3061 306F - FF01 000A 0001 0002 |んにちは!...| + 00000018 0003 0004 0005 0006 - 0007 0008 0009 000A |........| + 00000020 000B 000C 000D 000E - 000F 0010 0011 0012 |........| + 00000028 0013 0014 0015 0016 - 0017 0018 0019 001A |........| + 00000030 001B 001C 001D 001E - 001F 0020 0021 0022 |..... !"| + 00000038 0023 0024 0025 0026 - 0027 0028 0029 002A |#$%&'()*| + 00000040 002B 002C 002D 002E - 002F 0030 0031 0032 |+,-./012| + 00000048 0033 0034 0035 0036 - 0037 0038 0039 003A |3456789:| + 00000050 003B 003C 003D 003E - 003F 0040 0041 0042 |;<=>?@AB| + 00000058 0043 0044 0045 0046 - 0047 0048 0049 004A |CDEFGHIJ| + 00000060 004B 004C 004D 004E - 004F 0050 0051 0052 |KLMNOPQR| + 00000068 0053 0054 0055 0056 - 0057 0058 0059 005A |STUVWXYZ| + 00000070 005B 005C 005D 005E - 005F 0060 0061 0062 |[\]^_`ab| + 00000078 0063 0064 0065 0066 - 0067 0068 0069 006A |cdefghij| + 00000080 006B 006C 006D 006E - 006F 0070 0071 0072 |klmnopqr| + 00000088 0073 0074 0075 0076 - 0077 0078 0079 007A |stuvwxyz| + 00000090 007B 007C 007D 007E - 007F 0080 0081 0082 |{|}~....| + 00000098 0083 0084 0085 0086 - 0087 0088 0089 008A |........| + 000000A0 008B 008C 008D 008E - 008F 0090 0091 0092 |........| + 000000A8 0093 0094 0095 0096 - 0097 0098 0099 009A |........| + 000000B0 009B 009C 009D 009E - 009F 00A0 00A1 00A2 |..... ¡¢| + 000000B8 00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA |£¤¥¦§¨©ª| + 000000C0 00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2 |«¬®¯°±²| + 000000C8 00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA |³´µ¶·¸¹º| + 000000D0 00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2 |»¼½¾¿ÀÁÂ| + 000000D8 00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA |ÃÄÅÆÇÈÉÊ| + 000000E0 00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2 |ËÌÍÎÏÐÑÒ| + 000000E8 00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA |ÓÔÕÖרÙÚ| + 000000F0 00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2 |ÛÜÝÞßàáâ| + 000000F8 00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA |ãäåæçèéê| + 00000100 00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2 |ëìíîïðñò| + 00000108 00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA |óôõö÷øùú| + 00000110 00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A |ûüýþÿ.�.| + 00000118 EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80 |�.���.��| + 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���| + 00000128 EFBE EFEF EFBF EFBF - 000A 007A 000A |����.z.| +--- +name: integer-base-one-3Ar +description: + some sample code for hexdumping; NUL and binary safe +stdin: + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\x${i#16#1}" + done + print '\0z' + } | { + # integer-base-one-3Ar + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z5 hv=2147483647 + dasc= + if read -arN -1 line; then + typeset -i1 line + i=0 + while (( i < ${#line[*]} )); do + hv=${line[i++]} + if (( (pos & 15) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc=$dasc. + else + dasc=$dasc${line[i-1]#1#} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + fi + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" + } +expected-stdout: + 00000000 48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3 |Hello, World!\..| + 00000010 81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC |................| + 00000020 81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E |................| + 00000030 0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E |................| + 00000040 1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E |. !"#$%&'()*+,-.| + 00000050 2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E |/0123456789:;<=>| + 00000060 3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E |?@ABCDEFGHIJKLMN| + 00000070 4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E |OPQRSTUVWXYZ[\]^| + 00000080 5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E |_`abcdefghijklmn| + 00000090 6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E |opqrstuvwxyz{|}~| + 000000A0 7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E |................| + 000000B0 8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E |................| + 000000C0 9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE |................| + 000000D0 AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE |................| + 000000E0 BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE |................| + 000000F0 CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE |................| + 00000100 DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE |................| + 00000110 EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE |................| + 00000120 FF 00 7A 0A - |..z.| +--- +name: integer-base-one-3Wr +description: + some sample code for hexdumping Unicode; NUL and binary safe +stdin: + set -U + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\u${i#16#1}" + done + print + print \\xff # invalid utf-8 + print \\xc2 # invalid 2-byte + print \\xef\\xbf\\xc0 # invalid 3-byte + print \\xc0\\x80 # non-minimalistic + print \\xe0\\x80\\x80 # non-minimalistic + print '�' # end of range + print '\0z' # embedded NUL + } | { + # integer-base-one-3Wr + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z7 hv=2147483647 + dasc= + if read -arN -1 line; then + typeset -i1 line + i=0 + while (( i < ${#line[*]} )); do + hv=${line[i++]} + if (( (hv < 32) || \ + ((hv > 126) && (hv < 160)) )); then + dch=. + elif (( (hv & 0xFF80) == 0xEF80 )); then + dch=� + else + dch=${line[i-1]#1#} + fi + if (( (pos & 7) == 7 )); then + dasc=$dasc$dch + dch= + elif (( (pos & 7) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + (( (pos++ & 7) == 3 )) && \ + print -n -- '- ' + dasc=$dasc$dch done - print "$dasc|" fi + while (( pos & 7 )); do + print -n ' ' + (( (pos++ & 7) == 3 )) && print -n -- '- ' + done + (( hv == 2147483647 )) || print "$dasc|" } expected-stdout: 00000000 0048 0065 006C 006C - 006F 002C 0020 0057 |Hello, W| @@ -6417,7 +7291,7 @@ expected-stdout: 00000110 00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A |ûüýþÿ.�.| 00000118 EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80 |�.���.��| 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���| - 00000128 EFBE EFEF EFBF EFBF - 000A |����.| + 00000128 EFBE EFEF EFBF EFBF - 000A 0000 007A 000A |����..z.| --- name: integer-base-one-4 description: @@ -6440,6 +7314,32 @@ expected-stdout: 5 97 6 97 --- +name: integer-base-one-5A +description: + Check to see that we’re NUL and Unicode safe +stdin: + set +U + print 'a\0b\xfdz' >x + read -a y <x + set -U + typeset -Uui16 y + print ${y[*]} . +expected-stdout: + 16#61 16#0 16#62 16#FD 16#7A . +--- +name: integer-base-one-5W +description: + Check to see that we’re NUL and Unicode safe +stdin: + set -U + print 'a\0b€c' >x + read -a y <x + set +U + typeset -Uui16 y + print ${y[*]} . +expected-stdout: + 16#61 16#0 16#62 16#20AC 16#63 . +--- name: ulimit-1 description: Check if we can use a specific syntax idiom for ulimit @@ -6555,7 +7455,7 @@ stdin: expected-stdout: === mir -expected-stderr-pattern: /.*: cannot (create|overwrite) .*/ +expected-stderr-pattern: /.*: can't (create|overwrite) .*/ --- name: bashiop-3b description: @@ -6720,21 +7620,22 @@ stdin: name: fd-cloexec-1 description: Verify that file descriptors > 2 are private for Korn shells + AT&T ksh93 does this still, which means we must keep it as well file-setup: file 644 "test.sh" - print -u3 Fowl + echo >&3 Fowl stdin: exec 3>&1 "$__progname" test.sh expected-exit: e != 0 -expected-stderr: - test.sh[1]: print: -u: 3: bad file descriptor +expected-stderr-pattern: + /bad file descriptor/ --- name: fd-cloexec-2 description: Verify that file descriptors > 2 are not private for POSIX shells See Debian Bug #154540, Closes: #499139 file-setup: file 644 "test.sh" - print -u3 Fowl + echo >&3 Fowl stdin: test -n "$POSH_VERSION" || set -o sh exec 3>&1 @@ -6742,28 +7643,61 @@ stdin: expected-stdout: Fowl --- -name: comsub-1 +name: comsub-1a description: - COMSUB are currently parsed by hacking lex.c instead of - recursively (see regression-6): matching parenthesēs bug - Fails on: pdksh mksh bash2 bash3 zsh - Passes on: bash4 ksh93 -expected-fail: yes + COMSUB are now parsed recursively, so this works + see also regression-6: matching parenthesēs bug + Fails on: pdksh bash2 bash3 zsh + Passes on: bash4 ksh93 mksh(20110313+) stdin: echo $(case 1 in (1) echo yes;; (2) echo no;; esac) echo $(case 1 in 1) echo yes;; 2) echo no;; esac) + TEST=1234; echo ${TEST: $(case 1 in (1) echo 1;; (*) echo 2;; esac)} + TEST=5678; echo ${TEST: $(case 1 in 1) echo 1;; *) echo 2;; esac)} expected-stdout: yes yes + 234 + 678 +--- +name: comsub-1b +description: + COMSUB are now parsed recursively, so this works + Fails on GNU bash even, ksh93 passes +stdin: + echo $(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10)) + echo $(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20)) + (( a = $(case 1 in (1) echo 1;; (*) echo 2;; esac) )); echo $a. + (( a = $(case 1 in 1) echo 1;; *) echo 2;; esac) )); echo $a. +expected-stdout: + 11 + 21 + 1. + 1. +--- +name: comsub-1c +description: + COMSUB are now parsed recursively, so this works (ksh93, mksh) + First test passes on bash4, second fails there +category: !smksh +stdin: + a=($(case 1 in (1) echo 1;; (*) echo 2;; esac)); echo ${a[0]}. + a=($(case 1 in 1) echo 1;; *) echo 2;; esac)); echo ${a[0]}. + a=($(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))); echo ${a[0]}. + a=($(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))); echo ${a[0]}. +expected-stdout: + 1. + 1. + 11. + 21. --- name: comsub-2 description: RedHat BZ#496791 – another case of missing recursion in parsing COMSUB expressions - Fails on: pdksh mksh bash2 bash3¹ bash4¹ zsh - Passes on: ksh93 + Fails on: pdksh bash2 bash3¹ bash4¹ zsh + Passes on: ksh93 mksh(20110305+) ① bash[34] seem to choke on comment ending with backslash-newline -expected-fail: yes stdin: # a comment with " ' \ x=$( @@ -6774,6 +7708,707 @@ stdin: expected-stdout: yes --- +name: comsub-3 +description: + Extended test for COMSUB explaining why a recursive parser + is a must (a non-recursive parser cannot pass all three of + these test cases, especially the ‘#’ is difficult) +stdin: + echo $(typeset -i10 x=16#20; echo $x) + echo $(typeset -Uui16 x=16#$(id -u) + ) . + echo $(c=1; d=1 + typeset -Uui16 a=36#foo; c=2 + typeset -Uui16 b=36 #foo; d=2 + echo $a $b $c $d) +expected-stdout: + 32 + . + 16#4F68 16#24 2 1 +--- +name: comsub-4 +description: + Check the tree dump functions for !MKSH_SMALL functionality +category: !smksh +stdin: + x() { case $1 in a) a+=b ;;& *) c+=(d e) ;; esac; } + typeset -f x +expected-stdout: + x() { + case $1 in + (a) + a+=b + ;| + (*) + set -A c+ -- d e + ;; + esac + } +--- +name: comsub-torture +description: + Check the tree dump functions work correctly +stdin: + if [[ -z $__progname ]]; then echo >&2 call me with __progname; exit 1; fi + while IFS= read -r line; do + if [[ $line = '#1' ]]; then + lastf=0 + continue + elif [[ $line = EOFN* ]]; then + fbody=$fbody$'\n'$line + continue + elif [[ $line != '#'* ]]; then + fbody=$fbody$'\n\t'$line + continue + fi + if (( lastf )); then + x="inline_${nextf}() {"$fbody$'\n}\n' + print -nr -- "$x" + print -r -- "${x}typeset -f inline_$nextf" | "$__progname" + x="function comsub_$nextf { x=\$("$fbody$'\n); }\n' + print -nr -- "$x" + print -r -- "${x}typeset -f comsub_$nextf" | "$__progname" + x="function reread_$nextf { x=\$(("$fbody$'\n)|tr u x); }\n' + print -nr -- "$x" + print -r -- "${x}typeset -f reread_$nextf" | "$__progname" + fi + lastf=1 + fbody= + nextf=${line#?} + done <<'EOD' + #1 + #TCOM + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" + #TPAREN_TPIPE_TLIST + (echo $foo | tr -dc 0-9; echo) + #TAND_TOR + cmd && echo ja || echo nein + #TSELECT + select file in *; do echo "<$file>" ; break ; done + #TFOR_TTIME + for i in {1,2,3} ; do time echo $i ; done + #TCASE + case $foo in 1) echo eins;& 2) echo zwei ;| *) echo kann net bis drei zählen;; esac + #TIF_TBANG_TDBRACKET_TELIF + if ! [[ 1 = 1 ]] ; then echo eins; elif [[ 1 = 2 ]]; then echo zwei ;else echo drei; fi + #TWHILE + i=1; while (( i < 10 )); do echo $i; let ++i; done + #TUNTIL + i=10; until (( !--i )) ; do echo $i; done + #TCOPROC + cat * |& ls + #TFUNCT_TBRACE_TASYNC + function korn { echo eins; echo zwei ; } + bourne () { logger * & } + #IOREAD_IOCAT + tr x u 0<foo >>bar + #IOWRITE_IOCLOB_IOHERE_noIOSKIP + cat >|bar <<'EOFN' + foo + EOFN + #IOWRITE_noIOCLOB_IOHERE_IOSKIP + cat 1>bar <<-EOFI + foo + EOFI + #IORDWR_IODUP + sh 1<>/dev/console 0<&1 2>&1 + #COMSUB_EXPRSUB + echo $(true) $((1+ 2)) + #QCHAR_OQUOTE_CQUOTE + echo fo\ob\"a\`r\'b\$az + echo "fo\ob\"a\`r\'b\$az" + echo 'fo\ob\"a\`r'\''b\$az' + #OSUBST_CSUBST_OPAT_SPAT_CPAT + [[ ${foo#bl\(u\)b} = @(bar|baz) ]] + #heredoc_closed + x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN); echo $x + #heredoc_space + x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN ); echo $x + #patch_motd + x=$(sysctl -n kern.version | sed 1q) + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \ + ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF)" = @(?) ]] && rm -f /etc/motd + if [[ ! -s /etc/motd ]]; then + install -c -o root -g wheel -m 664 /dev/null /etc/motd + print -- "$x\n" >/etc/motd + fi + #0 + EOD +expected-stdout: + inline_TCOM() { + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" + } + inline_TCOM() { + vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" + } + function comsub_TCOM { x=$( + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" + ); } + function comsub_TCOM { + x=$(vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" ) + } + function reread_TCOM { x=$(( + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" + )|tr u x); } + function reread_TCOM { + x=$(( vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" ) | tr u x ) + } + inline_TPAREN_TPIPE_TLIST() { + (echo $foo | tr -dc 0-9; echo) + } + inline_TPAREN_TPIPE_TLIST() { + ( echo $foo | tr -dc 0-9 + echo ) + } + function comsub_TPAREN_TPIPE_TLIST { x=$( + (echo $foo | tr -dc 0-9; echo) + ); } + function comsub_TPAREN_TPIPE_TLIST { + x=$(( echo $foo | tr -dc 0-9 ; echo ) ) + } + function reread_TPAREN_TPIPE_TLIST { x=$(( + (echo $foo | tr -dc 0-9; echo) + )|tr u x); } + function reread_TPAREN_TPIPE_TLIST { + x=$(( ( echo $foo | tr -dc 0-9 ; echo ) ) | tr u x ) + } + inline_TAND_TOR() { + cmd && echo ja || echo nein + } + inline_TAND_TOR() { + cmd && echo ja || echo nein + } + function comsub_TAND_TOR { x=$( + cmd && echo ja || echo nein + ); } + function comsub_TAND_TOR { + x=$(cmd && echo ja || echo nein ) + } + function reread_TAND_TOR { x=$(( + cmd && echo ja || echo nein + )|tr u x); } + function reread_TAND_TOR { + x=$(( cmd && echo ja || echo nein ) | tr u x ) + } + inline_TSELECT() { + select file in *; do echo "<$file>" ; break ; done + } + inline_TSELECT() { + select file in * + do + echo "<$file>" + break + done + } + function comsub_TSELECT { x=$( + select file in *; do echo "<$file>" ; break ; done + ); } + function comsub_TSELECT { + x=$(select file in * ; do echo "<$file>" ; break ; done ) + } + function reread_TSELECT { x=$(( + select file in *; do echo "<$file>" ; break ; done + )|tr u x); } + function reread_TSELECT { + x=$(( select file in * ; do echo "<$file>" ; break ; done ) | tr u x ) + } + inline_TFOR_TTIME() { + for i in {1,2,3} ; do time echo $i ; done + } + inline_TFOR_TTIME() { + for i in {1,2,3} + do + time echo $i + done + } + function comsub_TFOR_TTIME { x=$( + for i in {1,2,3} ; do time echo $i ; done + ); } + function comsub_TFOR_TTIME { + x=$(for i in {1,2,3} ; do time echo $i ; done ) + } + function reread_TFOR_TTIME { x=$(( + for i in {1,2,3} ; do time echo $i ; done + )|tr u x); } + function reread_TFOR_TTIME { + x=$(( for i in {1,2,3} ; do time echo $i ; done ) | tr u x ) + } + inline_TCASE() { + case $foo in 1) echo eins;& 2) echo zwei ;| *) echo kann net bis drei zählen;; esac + } + inline_TCASE() { + case $foo in + (1) + echo eins + ;& + (2) + echo zwei + ;| + (*) + echo kann net bis drei zählen + ;; + esac + } + function comsub_TCASE { x=$( + case $foo in 1) echo eins;& 2) echo zwei ;| *) echo kann net bis drei zählen;; esac + ); } + function comsub_TCASE { + x=$(case $foo in (1) echo eins ;& (2) echo zwei ;| (*) echo kann net bis drei zählen ;; esac ) + } + function reread_TCASE { x=$(( + case $foo in 1) echo eins;& 2) echo zwei ;| *) echo kann net bis drei zählen;; esac + )|tr u x); } + function reread_TCASE { + x=$(( case $foo in (1) echo eins ;& (2) echo zwei ;| (*) echo kann net bis drei zählen ;; esac ) | tr u x ) + } + inline_TIF_TBANG_TDBRACKET_TELIF() { + if ! [[ 1 = 1 ]] ; then echo eins; elif [[ 1 = 2 ]]; then echo zwei ;else echo drei; fi + } + inline_TIF_TBANG_TDBRACKET_TELIF() { + if ! [[ 1 = 1 ]] + then + echo eins + elif [[ 1 = 2 ]] + then + echo zwei + else + echo drei + fi + } + function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$( + if ! [[ 1 = 1 ]] ; then echo eins; elif [[ 1 = 2 ]]; then echo zwei ;else echo drei; fi + ); } + function comsub_TIF_TBANG_TDBRACKET_TELIF { + x=$(if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) + } + function reread_TIF_TBANG_TDBRACKET_TELIF { x=$(( + if ! [[ 1 = 1 ]] ; then echo eins; elif [[ 1 = 2 ]]; then echo zwei ;else echo drei; fi + )|tr u x); } + function reread_TIF_TBANG_TDBRACKET_TELIF { + x=$(( if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) | tr u x ) + } + inline_TWHILE() { + i=1; while (( i < 10 )); do echo $i; let ++i; done + } + inline_TWHILE() { + i=1 + while let " i < 10 " + do + echo $i + let ++i + done + } + function comsub_TWHILE { x=$( + i=1; while (( i < 10 )); do echo $i; let ++i; done + ); } + function comsub_TWHILE { + x=$(i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) + } + function reread_TWHILE { x=$(( + i=1; while (( i < 10 )); do echo $i; let ++i; done + )|tr u x); } + function reread_TWHILE { + x=$(( i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) | tr u x ) + } + inline_TUNTIL() { + i=10; until (( !--i )) ; do echo $i; done + } + inline_TUNTIL() { + i=10 + until let " !--i " + do + echo $i + done + } + function comsub_TUNTIL { x=$( + i=10; until (( !--i )) ; do echo $i; done + ); } + function comsub_TUNTIL { + x=$(i=10 ; until let " !--i " ; do echo $i ; done ) + } + function reread_TUNTIL { x=$(( + i=10; until (( !--i )) ; do echo $i; done + )|tr u x); } + function reread_TUNTIL { + x=$(( i=10 ; until let " !--i " ; do echo $i ; done ) | tr u x ) + } + inline_TCOPROC() { + cat * |& ls + } + inline_TCOPROC() { + cat * |& + ls + } + function comsub_TCOPROC { x=$( + cat * |& ls + ); } + function comsub_TCOPROC { + x=$(cat * |& ls ) + } + function reread_TCOPROC { x=$(( + cat * |& ls + )|tr u x); } + function reread_TCOPROC { + x=$(( cat * |& ls ) | tr u x ) + } + inline_TFUNCT_TBRACE_TASYNC() { + function korn { echo eins; echo zwei ; } + bourne () { logger * & } + } + inline_TFUNCT_TBRACE_TASYNC() { + function korn { + echo eins + echo zwei + } + bourne() { + logger * & + } + } + function comsub_TFUNCT_TBRACE_TASYNC { x=$( + function korn { echo eins; echo zwei ; } + bourne () { logger * & } + ); } + function comsub_TFUNCT_TBRACE_TASYNC { + x=$(function korn { echo eins ; echo zwei ; } ; bourne() { logger * & } ) + } + function reread_TFUNCT_TBRACE_TASYNC { x=$(( + function korn { echo eins; echo zwei ; } + bourne () { logger * & } + )|tr u x); } + function reread_TFUNCT_TBRACE_TASYNC { + x=$(( function korn { echo eins ; echo zwei ; } ; bourne() { logger * & } ) | tr u x ) + } + inline_IOREAD_IOCAT() { + tr x u 0<foo >>bar + } + inline_IOREAD_IOCAT() { + tr x u <foo >>bar + } + function comsub_IOREAD_IOCAT { x=$( + tr x u 0<foo >>bar + ); } + function comsub_IOREAD_IOCAT { + x=$(tr x u <foo >>bar ) + } + function reread_IOREAD_IOCAT { x=$(( + tr x u 0<foo >>bar + )|tr u x); } + function reread_IOREAD_IOCAT { + x=$(( tr x u <foo >>bar ) | tr u x ) + } + inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() { + cat >|bar <<'EOFN' + foo + EOFN + } + inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() { + cat >|bar <<"EOFN" + foo + EOFN + + } + function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$( + cat >|bar <<'EOFN' + foo + EOFN + ); } + function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP { + x=$(cat >|bar <<"EOFN" + foo + EOFN + ) + } + function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$(( + cat >|bar <<'EOFN' + foo + EOFN + )|tr u x); } + function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP { + x=$(( cat >|bar <<"EOFN" + foo + EOFN + ) | tr u x ) + } + inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() { + cat 1>bar <<-EOFI + foo + EOFI + } + inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() { + cat >bar <<-EOFI + foo + EOFI + + } + function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$( + cat 1>bar <<-EOFI + foo + EOFI + ); } + function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP { + x=$(cat >bar <<-EOFI + foo + EOFI + ) + } + function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$(( + cat 1>bar <<-EOFI + foo + EOFI + )|tr u x); } + function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP { + x=$(( cat >bar <<-EOFI + foo + EOFI + ) | tr u x ) + } + inline_IORDWR_IODUP() { + sh 1<>/dev/console 0<&1 2>&1 + } + inline_IORDWR_IODUP() { + sh 1<>/dev/console <&1 2>&1 + } + function comsub_IORDWR_IODUP { x=$( + sh 1<>/dev/console 0<&1 2>&1 + ); } + function comsub_IORDWR_IODUP { + x=$(sh 1<>/dev/console <&1 2>&1 ) + } + function reread_IORDWR_IODUP { x=$(( + sh 1<>/dev/console 0<&1 2>&1 + )|tr u x); } + function reread_IORDWR_IODUP { + x=$(( sh 1<>/dev/console <&1 2>&1 ) | tr u x ) + } + inline_COMSUB_EXPRSUB() { + echo $(true) $((1+ 2)) + } + inline_COMSUB_EXPRSUB() { + echo $(true ) $((1+ 2)) + } + function comsub_COMSUB_EXPRSUB { x=$( + echo $(true) $((1+ 2)) + ); } + function comsub_COMSUB_EXPRSUB { + x=$(echo $(true ) $((1+ 2)) ) + } + function reread_COMSUB_EXPRSUB { x=$(( + echo $(true) $((1+ 2)) + )|tr u x); } + function reread_COMSUB_EXPRSUB { + x=$(( echo $(true ) $((1+ 2)) ) | tr u x ) + } + inline_QCHAR_OQUOTE_CQUOTE() { + echo fo\ob\"a\`r\'b\$az + echo "fo\ob\"a\`r\'b\$az" + echo 'fo\ob\"a\`r'\''b\$az' + } + inline_QCHAR_OQUOTE_CQUOTE() { + echo fo\ob\"a\`r\'b\$az + echo "fo\ob\"a\`r\'b\$az" + echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" + } + function comsub_QCHAR_OQUOTE_CQUOTE { x=$( + echo fo\ob\"a\`r\'b\$az + echo "fo\ob\"a\`r\'b\$az" + echo 'fo\ob\"a\`r'\''b\$az' + ); } + function comsub_QCHAR_OQUOTE_CQUOTE { + x=$(echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) + } + function reread_QCHAR_OQUOTE_CQUOTE { x=$(( + echo fo\ob\"a\`r\'b\$az + echo "fo\ob\"a\`r\'b\$az" + echo 'fo\ob\"a\`r'\''b\$az' + )|tr u x); } + function reread_QCHAR_OQUOTE_CQUOTE { + x=$(( echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) | tr u x ) + } + inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() { + [[ ${foo#bl\(u\)b} = @(bar|baz) ]] + } + inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() { + [[ ${foo#bl\(u\)b} = @(bar|baz) ]] + } + function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$( + [[ ${foo#bl\(u\)b} = @(bar|baz) ]] + ); } + function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT { + x=$([[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) + } + function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$(( + [[ ${foo#bl\(u\)b} = @(bar|baz) ]] + )|tr u x); } + function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT { + x=$(( [[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) | tr u x ) + } + inline_heredoc_closed() { + x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN); echo $x + } + inline_heredoc_closed() { + x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN + ) + echo $x + } + function comsub_heredoc_closed { x=$( + x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN); echo $x + ); } + function comsub_heredoc_closed { + x=$(x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN + ) ; echo $x ) + } + function reread_heredoc_closed { x=$(( + x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN); echo $x + )|tr u x); } + function reread_heredoc_closed { + x=$(( x=$(cat <<EOFN + note there must be no space between EOFN and ) + EOFN + ) ; echo $x ) | tr u x ) + } + inline_heredoc_space() { + x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN ); echo $x + } + inline_heredoc_space() { + x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN + ) + echo $x + } + function comsub_heredoc_space { x=$( + x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN ); echo $x + ); } + function comsub_heredoc_space { + x=$(x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN + ) ; echo $x ) + } + function reread_heredoc_space { x=$(( + x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN ); echo $x + )|tr u x); } + function reread_heredoc_space { + x=$(( x=$(cat <<EOFN\ + note the space between EOFN and ) is actually part of the here document marker + EOFN + ) ; echo $x ) | tr u x ) + } + inline_patch_motd() { + x=$(sysctl -n kern.version | sed 1q) + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \ + ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF)" = @(?) ]] && rm -f /etc/motd + if [[ ! -s /etc/motd ]]; then + install -c -o root -g wheel -m 664 /dev/null /etc/motd + print -- "$x\n" >/etc/motd + fi + } + inline_patch_motd() { + x=$(sysctl -n kern.version | sed 1q ) + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF + )" = @(?) ]] && rm -f /etc/motd + if [[ ! -s /etc/motd ]] + then + install -c -o root -g wheel -m 664 /dev/null /etc/motd + print -- "$x\n" >/etc/motd + fi + } + function comsub_patch_motd { x=$( + x=$(sysctl -n kern.version | sed 1q) + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \ + ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF)" = @(?) ]] && rm -f /etc/motd + if [[ ! -s /etc/motd ]]; then + install -c -o root -g wheel -m 664 /dev/null /etc/motd + print -- "$x\n" >/etc/motd + fi + ); } + function comsub_patch_motd { + x=$(x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF + )" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) + } + function reread_patch_motd { x=$(( + x=$(sysctl -n kern.version | sed 1q) + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \ + ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF)" = @(?) ]] && rm -f /etc/motd + if [[ ! -s /etc/motd ]]; then + install -c -o root -g wheel -m 664 /dev/null /etc/motd + print -- "$x\n" >/etc/motd + fi + )|tr u x); } + function reread_patch_motd { + x=$(( x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF + 1,/^\$/d + 0a + $x + + . + wq + EOF + )" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) | tr u x ) + } +--- name: test-stnze-1 description: Check that the short form [ $x ] works @@ -6875,6 +8510,7 @@ file-setup: file 755 "falsetto" file-setup: file 755 "!false" #! /bin/sh echo si +need-ctty: yes arguments: !-i! stdin: export PATH=.:$PATH @@ -6903,6 +8539,7 @@ file-setup: file 755 "falsetto" file-setup: file 755 "!" #! /bin/sh echo si +need-ctty: yes arguments: !-i! stdin: export PATH=.:$PATH @@ -6929,6 +8566,7 @@ file-setup: file 755 "falsetto" file-setup: file 755 "!" #! /bin/sh echo si +need-ctty: yes arguments: !-i! env-setup: !ENV=./Env! file-setup: file 644 "Env" @@ -7152,11 +8790,28 @@ expected-stdout: 2 a . 3 . --- +name: nameref-4 +description: + Ensure we don't run in an infinite loop +time-limit: 3 +stdin: + baz() { + typeset -n foo=foo + foo[0]=bar + } + set -A foo bad + echo sind $foo . + baz + echo blah $foo . +expected-stdout: + sind bad . + blah bar . +--- name: better-parens-1a description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - if ( (echo fubar) | tr u x); then + if ( (echo fubar)|tr u x); then echo ja else echo nein @@ -7169,7 +8824,15 @@ name: better-parens-1b description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - echo $( (echo fubar) | tr u x) $? + echo $( (echo fubar)|tr u x) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-1c +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + x=$( (echo fubar)|tr u x); echo $x $? expected-stdout: fxbar 0 --- @@ -7177,7 +8840,7 @@ name: better-parens-2a description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - if ((echo fubar) | tr u x); then + if ((echo fubar)|tr u x); then echo ja else echo nein @@ -7190,7 +8853,15 @@ name: better-parens-2b description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - echo $((echo fubar) | tr u x) $? + echo $((echo fubar)|tr u x) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-2c +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + x=$((echo fubar)|tr u x); echo $x $? expected-stdout: fxbar 0 --- @@ -7198,7 +8869,7 @@ name: better-parens-3a description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - if ( (echo fubar) | (tr u x)); then + if ( (echo fubar)|(tr u x)); then echo ja else echo nein @@ -7211,7 +8882,15 @@ name: better-parens-3b description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - echo $( (echo fubar) | (tr u x)) $? + echo $( (echo fubar)|(tr u x)) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-3c +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + x=$( (echo fubar)|(tr u x)); echo $x $? expected-stdout: fxbar 0 --- @@ -7219,7 +8898,7 @@ name: better-parens-4a description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - if ((echo fubar) | (tr u x)); then + if ((echo fubar)|(tr u x)); then echo ja else echo nein @@ -7232,7 +8911,15 @@ name: better-parens-4b description: Check support for ((…)) and $((…)) vs (…) and $(…) stdin: - echo $((echo fubar) | (tr u x)) $? + echo $((echo fubar)|(tr u x)) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-4c +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + x=$((echo fubar)|(tr u x)); echo $x $? expected-stdout: fxbar 0 --- @@ -7441,3 +9128,117 @@ expected-stdout: 24 ?lnnix/nix =lnnix/nix: No such file or directory !2 25 ?lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself =lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself: Too many levels of symbolic links !62 --- +name: realpath-2 +description: + Ensure that exactly two leading slashes are not collapsed + POSIX guarantees this exception, e.g. for UNC paths on Cygwin +category: os:mirbsd +stdin: + ln -s /bin t1 + ln -s //bin t2 + ln -s ///bin t3 + realpath /bin + realpath //bin + realpath ///bin + realpath /usr/bin + realpath /usr//bin + realpath /usr///bin + realpath t1 + realpath t2 + realpath t3 + rm -f t1 t2 t3 + cd //usr/bin + pwd + cd ../lib + pwd + realpath //usr/include/../bin +expected-stdout: + /bin + //bin + /bin + /usr/bin + /usr/bin + /usr/bin + /bin + //bin + /bin + //usr/bin + //usr/lib + //usr/bin +--- +name: crash-1 +description: + Crashed during March 2011, fixed on vernal equinōx ☺ +category: os:mirbsd,os:openbsd +stdin: + export MALLOC_OPTIONS=FGJPRSX + "$__progname" -c 'x=$(tr z r <<<baz); echo $x' +expected-stdout: + bar +--- +name: debian-117-1 +description: + Check test - bug#465250 +stdin: + test \( ! -e \) ; echo $? +expected-stdout: + 1 +--- +name: debian-117-2 +description: + Check test - bug#465250 +stdin: + test \( -e \) ; echo $? +expected-stdout: + 0 +--- +name: debian-117-3 +description: + Check test - bug#465250 +stdin: + test ! -e ; echo $? +expected-stdout: + 1 +--- +name: debian-117-4 +description: + Check test - bug#465250 +stdin: + test -e ; echo $? +expected-stdout: + 0 +--- +name: case-zsh +description: + Check that zsh case variants work +stdin: + case 'b' in + a) echo a ;; + b) echo b ;; + c) echo c ;; + *) echo x ;; + esac + echo = + case 'b' in + a) echo a ;& + b) echo b ;& + c) echo c ;& + *) echo x ;& + esac + echo = + case 'b' in + a) echo a ;| + b) echo b ;| + c) echo c ;| + *) echo x ;| + esac +expected-stdout: + b + = + b + c + x + = + b + x +--- diff --git a/src/dot.mkshrc b/src/dot.mkshrc new file mode 100644 index 0000000..518f031 --- /dev/null +++ b/src/dot.mkshrc @@ -0,0 +1,375 @@ +# $Id$ +# $MirOS: src/bin/mksh/dot.mkshrc,v 1.65 2011/08/27 18:06:40 tg Exp $ +#- +# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 +# Thorsten Glaser <tg@mirbsd.org> +# +# Provided that these terms and disclaimer and all copyright notices +# are retained or reproduced in an accompanying document, permission +# is granted to deal in this work without restriction, including un- +# limited rights to use, publicly perform, distribute, sell, modify, +# merge, give away, or sublicence. +# +# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to +# the utmost extent permitted by applicable law, neither express nor +# implied; without malicious intent or gross negligence. In no event +# may a licensor, author or contributor be held liable for indirect, +# direct, other damage, loss, or other issues arising in any way out +# of dealing in the work, even if advised of the possibility of such +# damage or existence of a defect, except proven that it results out +# of said person's immediate fault when using the work as intended. +#- +# ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells + +: ${EDITOR:=/bin/ed} ${TERM:=vt100} ${HOSTNAME:=$(ulimit -c 0;hostname -s 2>&-)} +[[ $HOSTNAME = @(localhost|*([ ])) ]] && HOSTNAME=$(ulimit -c 0;hostname 2>&-) +: ${HOSTNAME:=nil}; if (( USER_ID )); then PS1='$'; else PS1='#'; fi +function precmd { + local e=$? + + (( e )) && print -n "$e|" +} +PS1=$'\001\r''$(precmd)${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \? + )}@${HOSTNAME%%.*}:$(local d=${PWD:-?} p=~; [[ $p = ?(*/) ]] || \ + d=${d/#$p/~}; local m=${%d} n p=...; (( m > 0 )) || m=${#d} + (( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || \ + p=; print -nr -- "$p$d") '"$PS1 " +: ${MKSH:=$(whence -p mksh)}; export EDITOR HOSTNAME MKSH TERM USER +alias ls=ls +unalias ls +alias l='ls -F' +alias la='l -a' +alias ll='l -l' +alias lo='l -alo' +whence -p rot13 >&- || alias rot13='tr \ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \ + nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' +whence -p hd >&- || function hd { + hexdump -e '"%08.8_ax " 8/1 "%02X " " - " 8/1 "%02X "' \ + -e '" |" "%_p"' -e '"|\n"' "$@" +} + +# Berkeley C shell compatible dirs, popd, and pushd functions +# Z shell compatible chpwd() hook, used to update DIRSTACK[0] +DIRSTACKBASE=$(realpath ~/. 2>&- || print -nr -- "$HOME") +set -A DIRSTACK +function chpwd { + DIRSTACK[0]=$(realpath . 2>&- || print -r -- "$PWD") + [[ $DIRSTACKBASE = ?(*/) ]] || \ + DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/~} + : +} +chpwd . +function cd { + builtin cd "$@" + chpwd "$@" +} +function cd_csh { + local d t=${1/#~/$DIRSTACKBASE} + + if ! d=$(builtin cd "$t" 2>&1); then + print -u2 "${1}: ${d##*$t - }." + return 1 + fi + cd "$t" +} +function dirs { + local d dwidth + local -i isnoglob=0 fl=0 fv=0 fn=0 cpos=0 + + [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1 + set -o noglob + while getopts ":lvn" d; do + case $d { + (l) fl=1 ;; + (v) fv=1 ;; + (n) fn=1 ;; + (*) print -u2 'Usage: dirs [-lvn].' + return 1 ;; + } + done + shift $((OPTIND - 1)) + if (( $# > 0 )); then + print -u2 'Usage: dirs [-lvn].' + return 1 + fi + if (( fv )); then + fv=0 + while (( fv < ${#DIRSTACK[*]} )); do + d=${DIRSTACK[fv]} + (( fl )) && d=${d/#~/$DIRSTACKBASE} + print -r -- "$fv $d" + let fv++ + done + else + fv=0 + while (( fv < ${#DIRSTACK[*]} )); do + d=${DIRSTACK[fv]} + (( fl )) && d=${d/#~/$DIRSTACKBASE} + (( dwidth = (${%d} > 0 ? ${%d} : ${#d}) )) + if (( fn && (cpos += dwidth + 1) >= 79 && \ + dwidth < 80 )); then + print + (( cpos = dwidth + 1 )) + fi + print -nr -- "$d " + let fv++ + done + print + fi + (( isnoglob )) || set +o noglob + return 0 +} +function popd { + local d fa + local -i isnoglob=0 n=1 + + [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1 + set -o noglob + while getopts ":0123456789lvn" d; do + case $d { + (l|v|n) fa="$fa -$d" ;; + (+*) n=2 + break ;; + (*) print -u2 'Usage: popd [-lvn] [+<n>].' + return 1 ;; + } + done + shift $((OPTIND - n)) + n=0 + if (( $# > 1 )); then + print -u2 popd: Too many arguments. + return 1 + elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then + if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then + print -u2 popd: Directory stack not that deep. + return 1 + fi + elif [[ -n $1 ]]; then + print -u2 popd: Bad directory. + return 1 + fi + if (( ${#DIRSTACK[*]} < 2 )); then + print -u2 popd: Directory stack empty. + return 1 + fi + unset DIRSTACK[n] + set -A DIRSTACK -- "${DIRSTACK[@]}" + cd_csh "${DIRSTACK[0]}" || return 1 + (( isnoglob )) || set +o noglob + dirs $fa +} +function pushd { + local d fa + local -i isnoglob=0 n=1 + + [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1 + set -o noglob + while getopts ":0123456789lvn" d; do + case $d { + (l|v|n) fa="$fa -$d" ;; + (+*) n=2 + break ;; + (*) print -u2 'Usage: pushd [-lvn] [<dir>|+<n>].' + return 1 ;; + } + done + shift $((OPTIND - n)) + if (( $# == 0 )); then + if (( ${#DIRSTACK[*]} < 2 )); then + print -u2 pushd: No other directory. + return 1 + fi + d=${DIRSTACK[1]} + DIRSTACK[1]=${DIRSTACK[0]} + cd_csh "$d" || return 1 + elif (( $# > 1 )); then + print -u2 pushd: Too many arguments. + return 1 + elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then + if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then + print -u2 pushd: Directory stack not that deep. + return 1 + fi + while (( n-- )); do + d=${DIRSTACK[0]} + unset DIRSTACK[0] + set -A DIRSTACK -- "${DIRSTACK[@]}" "$d" + done + cd_csh "${DIRSTACK[0]}" || return 1 + else + set -A DIRSTACK -- placeholder "${DIRSTACK[@]}" + cd_csh "$1" || return 1 + fi + (( isnoglob )) || set +o noglob + dirs $fa +} + +# pager (not control character safe) +function smores { + local dummy line llen curlin=0 + + cat "$@" | while IFS= read -r line; do + llen=${%line} + (( llen == -1 )) && llen=${#line} + (( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 )) + if (( (curlin += llen) >= LINES )); then + print -n -- '\033[7m--more--\033[0m' + read -u1 dummy + [[ $dummy = [Qq]* ]] && return 0 + curlin=$llen + fi + print -r -- "$line" + done +} + +# base64 encoder and decoder, RFC compliant, NUL safe +function Lb64decode { + [[ -o utf8-mode ]]; local u=$? + set +U + local c s="$*" t= + [[ -n $s ]] || { s=$(cat;print x); s=${s%x}; } + local -i i=0 n=${#s} p=0 v x + local -i16 o + + while (( i < n )); do + c=${s:(i++):1} + case $c { + (=) break ;; + ([A-Z]) (( v = 1#$c - 65 )) ;; + ([a-z]) (( v = 1#$c - 71 )) ;; + ([0-9]) (( v = 1#$c + 4 )) ;; + (+) v=62 ;; + (/) v=63 ;; + (*) continue ;; + } + (( x = (x << 6) | v )) + case $((p++)) { + (0) continue ;; + (1) (( o = (x >> 4) & 255 )) ;; + (2) (( o = (x >> 2) & 255 )) ;; + (3) (( o = x & 255 )) + p=0 + ;; + } + t=$t\\x${o#16#} + done + print -n $t + (( u )) || set -U +} + +set -A Lb64encode_code -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ + a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + / +function Lb64encode { + [[ -o utf8-mode ]]; local u=$? + set +U + local c s t + if (( $# )); then + read -raN-1 s <<<"$*" + unset s[${#s[*]}-1] + else + read -raN-1 s + fi + local -i i=0 n=${#s[*]} j v + + while (( i < n )); do + (( v = s[i++] << 16 )) + (( j = i < n ? s[i++] : 0 )) + (( v |= j << 8 )) + (( j = i < n ? s[i++] : 0 )) + (( v |= j )) + t=$t${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]} + c=${Lb64encode_code[v >> 6 & 63]} + if (( i <= n )); then + t=$t$c${Lb64encode_code[v & 63]} + elif (( i == n + 1 )); then + t=$t$c= + else + t=$t== + fi + if (( ${#t} == 76 || i >= n )); then + print $t + t= + fi + done + (( u )) || set -U +} + +# mksh NUL counting, never zero +typeset -Z11 -Uui16 Lnzathash_v +function Lnzathash_add { + [[ -o utf8-mode ]]; local u=$? + set +U + local s + if (( $# )); then + read -raN-1 s <<<"$*" + unset s[${#s[*]}-1] + else + read -raN-1 s + fi + local -i i=0 n=${#s[*]} + + while (( i < n )); do + ((# Lnzathash_v = (Lnzathash_v + s[i++] + 1) * 1025 )) + ((# Lnzathash_v ^= Lnzathash_v >> 6 )) + done + + (( u )) || set -U +} +function Lnzaathash_end { + ((# Lnzathash_v *= 1025 )) + ((# Lnzathash_v ^= Lnzathash_v >> 6 )) + ((# Lnzathash_v += Lnzathash_v << 3 )) + ((# Lnzathash_v = (Lnzathash_v ^ + (Lnzathash_v >> 11)) * 32769 )) + print ${Lnzathash_v#16#} +} +function Lnzaathash { + Lnzathash_v=0 + Lnzathash_add "$@" + Lnzaathash_end +} +function Lnzathash { + Lnzathash_v=0 + Lnzathash_add "$@" + if (( Lnzathash_v )); then + Lnzaathash_end + else + Lnzathash_v=1 + print ${Lnzathash_v#16#} + fi +} + +# strip comments (and leading/trailing whitespace if IFS is set) from +# any file(s) given as argument, or stdin if none, and spew to stdout +function Lstripcom { + cat "$@" | { set -o noglob; while read _line; do + _line=${_line%%#*} + [[ -n $_line ]] && print -r -- $_line + done; } +} + +# give MidnightBSD's laffer1 a bit of csh feeling +function setenv { + eval export $1'="$2"' +} + +: place customisations below this line + +for p in ~/.etc/bin ~/bin; do + [[ -d $p/. ]] || continue + [[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH +done + +export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=- +alias cls='print -n \\033c' + +#unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \ +# LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME +#p=en_GB.UTF-8 +#set -U +#export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p + +unset p + +: place customisations above this line @@ -1,10 +1,10 @@ /* $OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $ */ -/* $OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $ */ -/* $OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $ */ +/* $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $ */ +/* $OpenBSD: emacs.c,v 1.44 2011/09/05 04:50:33 marco Exp $ */ /* $OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -25,7 +25,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.222 2011/10/07 19:45:08 tg Exp $"); /* * in later versions we might use libtermcap for this, but since external @@ -52,11 +52,14 @@ typedef struct { static X_chars edchars; -/* x_fc_glob() flags */ +/* x_cf_glob() flags */ #define XCF_COMMAND BIT(0) /* Do command completion */ #define XCF_FILE BIT(1) /* Do file completion */ #define XCF_FULLPATH BIT(2) /* command completion: store full path */ -#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE) +#define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE) +#define XCF_IS_COMMAND BIT(3) /* return flag: is command */ +#define XCF_IS_SUBGLOB BIT(4) /* return flag: is $FOO or ~foo substitution */ +#define XCF_IS_EXTGLOB BIT(5) /* return flag: is foo* expansion */ static char editmode; static int xx_cols; /* for Emacs mode */ @@ -65,12 +68,11 @@ static char holdbuf[LINE]; /* place to hold last edit buffer */ static int x_getc(void); static void x_putcf(int); -static bool x_mode(bool); -static int x_do_comment(char *, int, int *); +static void x_mode(bool); +static int x_do_comment(char *, ssize_t, ssize_t *); static void x_print_expansions(int, char *const *, bool); -static int x_cf_glob(int, const char *, int, int, int *, int *, char ***, - bool *); -static int x_longest_prefix(int, char *const *); +static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***); +static size_t x_longest_prefix(int, char *const *); static int x_basename(const char *, const char *); static void x_free_words(int, char **); static int x_escape(const char *, size_t, int (*)(const char *, size_t)); @@ -89,17 +91,10 @@ static int x_vi(char *, size_t); #endif static int path_order_cmp(const void *aa, const void *bb); -static char *add_glob(const char *, int) - MKSH_A_NONNULL((nonnull (1))) - MKSH_A_BOUNDED(string, 1, 2); static void glob_table(const char *, XPtrV *, struct table *); static void glob_path(int flags, const char *, XPtrV *, const char *); -static int x_file_glob(int, const char *, int, char ***) - MKSH_A_NONNULL((nonnull (2))) - MKSH_A_BOUNDED(string, 2, 3); -static int x_command_glob(int, const char *, int, char ***) - MKSH_A_NONNULL((nonnull (2))) - MKSH_A_BOUNDED(string, 2, 3); +static int x_file_glob(int, char *, char ***); +static int x_command_glob(int, char *, char ***); static int x_locate_word(const char *, int, int, int *, bool *); static int x_e_getmbc(char *); @@ -111,11 +106,14 @@ static int x_e_rebuildline(const char *); void x_init(void) { - /* set to -2 to force initial binding */ + /* + * Set edchars to -2 to force initial binding, except + * we need default values for some deficient systems… + */ edchars.erase = edchars.kill = edchars.intr = edchars.quit = edchars.eof = -2; - /* default value for deficient systems */ - edchars.werase = 027; /* ^W */ + /* ^W */ + edchars.werase = 027; x_init_emacs(); } @@ -136,7 +134,8 @@ x_read(char *buf, size_t len) i = x_vi(buf, len); #endif else - i = -1; /* internal error */ + /* internal error */ + i = -1; editmode = 0; x_mode(false); return (i); @@ -148,7 +147,7 @@ static int x_getc(void) { char c; - int n; + ssize_t n; while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) if (trap) { @@ -179,7 +178,8 @@ x_putcf(int c) * Misc common code for vi/emacs * *********************************/ -/* Handle the commenting/uncommenting of a line. +/*- + * Handle the commenting/uncommenting of a line. * Returns: * 1 if a carriage return is indicated (comment added) * 0 if no return (comment removed) @@ -188,12 +188,13 @@ x_putcf(int c) * moved to the start of the line after (un)commenting. */ static int -x_do_comment(char *buf, int bsize, int *lenp) +x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp) { - int i, j, len = *lenp; + ssize_t i, j, len = *lenp; if (len == 0) - return (1); /* somewhat arbitrary - it's what AT&T ksh does */ + /* somewhat arbitrary - it's what AT&T ksh does */ + return (1); /* Already commented? */ if (buf[0] == '#') { @@ -238,7 +239,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command) int prefix_len; XPtrV l = { NULL, NULL, NULL }; - /* Check if all matches are in the same directory (in this + /* + * Check if all matches are in the same directory (in this * case, we want to omit the directory name) */ if (!is_command && @@ -272,7 +274,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command) pr_list(use_copy ? (char **)XPptrv(l) : words); if (use_copy) - XPfree(l); /* not x_free_words() */ + /* not x_free_words() */ + XPfree(l); } /** @@ -283,19 +286,14 @@ x_print_expansions(int nwords, char * const *words, bool is_command) * - returns number of matching strings */ static int -x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) +x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp) { - char *toglob, **words; + char **words; int nwords, i, idx; bool escaping; XPtrV w; struct source *s, *sold; - if (slen < 0) - return (0); - - toglob = add_glob(str, slen); - /* remove all escaping backward slashes */ escaping = false; for (i = 0, idx = 0; toglob[i]; i++) { @@ -324,7 +322,7 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) source = s; if (yylex(ONEWORD | LQCHAR) != LWORD) { source = sold; - internal_warningf("fileglob: substitute error"); + internal_warningf("%s: %s", "fileglob", "bad substitution"); return (0); } source = sold; @@ -338,8 +336,9 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) if (nwords == 1) { struct stat statb; - /* Check if globbing failed (returned glob pattern), - * but be careful (E.g. toglob == "ab*" when the file + /* + * Check if globbing failed (returned glob pattern), + * but be careful (e.g. toglob == "ab*" when the file * "ab*" exists is not an error). * Also, check for empty result - happens if we tried * to glob something which evaluated to an empty @@ -353,7 +352,6 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) nwords = 0; } } - afree(toglob, ATEMP); if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL) x_free_words(nwords, words); @@ -381,21 +379,15 @@ path_order_cmp(const void *aa, const void *bb) } static int -x_command_glob(int flags, const char *str, int slen, char ***wordsp) +x_command_glob(int flags, char *toglob, char ***wordsp) { - char *toglob, *pat, *fpath; + char *pat, *fpath; int nwords; XPtrV w; struct block *l; - if (slen < 0) - return (0); - - toglob = add_glob(str, slen); - /* Convert "foo*" (toglob) to a pattern for future use */ pat = evalstr(toglob, DOPAT | DOTILDE); - afree(toglob, ATEMP); XPinit(w, 32); @@ -424,7 +416,7 @@ x_command_glob(int flags, const char *str, int slen, char ***wordsp) int i, path_order = 0; info = (struct path_order_info *) - alloc(nwords * sizeof(struct path_order_info), ATEMP); + alloc2(nwords, sizeof(struct path_order_info), ATEMP); for (i = 0; i < nwords; i++) { info[i].word = words[i]; info[i].base = x_basename(words[i], NULL); @@ -481,7 +473,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp, /* The case where pos == buflen happens to take care of itself... */ start = pos; - /* Keep going backwards to start of word (has effect of allowing + /* + * Keep going backwards to start of word (has effect of allowing * one blank after the end of a word) */ for (; (start > 0 && IS_WORDC(buf[start - 1])) || @@ -502,7 +495,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp, p--; iscmd = p < 0 || vstrchr(";|&()`", buf[p]); if (iscmd) { - /* If command has a /, path, etc. is not searched; + /* + * If command has a /, path, etc. is not searched; * only current directory is searched which is just * like file globbing. */ @@ -519,86 +513,105 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp, } static int -x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, - int *endp, char ***wordsp, bool *is_commandp) +x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, + int *endp, char ***wordsp) { - int len, nwords; + int len, nwords = 0; char **words = NULL; bool is_command; len = x_locate_word(buf, buflen, pos, startp, &is_command); - if (!(flags & XCF_COMMAND)) + if (!((*flagsp) & XCF_COMMAND)) is_command = false; - /* Don't do command globing on zero length strings - it takes too + /* + * Don't do command globing on zero length strings - it takes too * long and isn't very useful. File globs are more likely to be * useful, so allow these. */ if (len == 0 && is_command) return (0); - nwords = is_command ? - x_command_glob(flags, buf + *startp, len, &words) : - x_file_glob(flags, buf + *startp, len, &words); + if (len >= 0) { + char *toglob, *s; + bool saw_dollar = false, saw_glob = false; + + /* + * Given a string, copy it and possibly add a '*' to the end. + */ + + strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP); + toglob[len] = '\0'; + + /* + * If the pathname contains a wildcard (an unquoted '*', + * '?', or '[') or parameter expansion ('$'), or a ~username + * with no trailing slash, then it is globbed based on that + * value (i.e., without the appended '*'). + */ + for (s = toglob; *s; s++) { + if (*s == '\\' && s[1]) + s++; + else if (*s == '$') { + /* + * Do not append a space after the value + * if expanding a parameter substitution + * as in: “cat $HOME/.ss↹” (LP: #710539) + */ + saw_dollar = true; + } else if (*s == '?' || *s == '*' || *s == '[' || + /* ?() *() +() @() !() but two already checked */ + (s[1] == '(' /*)*/ && + (*s == '+' || *s == '@' || *s == '!'))) { + /* just expand based on the extglob */ + saw_glob = true; + } + } + if (saw_glob) { + /* + * do not append a glob, we already have a + * glob or extglob; it works even if this is + * a parameter expansion as we have a glob + */ + *flagsp |= XCF_IS_EXTGLOB; + } else if (saw_dollar || + (*toglob == '~' && !vstrchr(toglob, '/'))) { + /* do not append a glob, nor later a space */ + *flagsp |= XCF_IS_SUBGLOB; + } else { + /* append a glob, this is not just a tilde */ + toglob[len] = '*'; + toglob[len + 1] = '\0'; + } + + /* + * Expand (glob) it now. + */ + + nwords = is_command ? + x_command_glob(*flagsp, toglob, &words) : + x_file_glob(*flagsp, toglob, &words); + afree(toglob, ATEMP); + } if (nwords == 0) { *wordsp = NULL; return (0); } - if (is_commandp) - *is_commandp = is_command; + if (is_command) + *flagsp |= XCF_IS_COMMAND; *wordsp = words; *endp = *startp + len; return (nwords); } -/* Given a string, copy it and possibly add a '*' to the end. - * The new string is returned. - */ -static char * -add_glob(const char *str, int slen) -{ - char *toglob, *s; - bool saw_slash = false; - - if (slen < 0) - return (NULL); - - /* for clang's static analyser, the nonnull attribute isn't enough */ - mkssert(str != NULL); - - strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */ - toglob[slen] = '\0'; - - /* - * If the pathname contains a wildcard (an unquoted '*', - * '?', or '[') or parameter expansion ('$'), or a ~username - * with no trailing slash, then it is globbed based on that - * value (i.e., without the appended '*'). - */ - for (s = toglob; *s; s++) { - if (*s == '\\' && s[1]) - s++; - else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' || - (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */ - (*s == '+' || *s == '@' || *s == '!'))) - break; - else if (*s == '/') - saw_slash = true; - } - if (!*s && (*toglob != '~' || saw_slash)) { - toglob[slen] = '*'; - toglob[slen + 1] = '\0'; - } - return (toglob); -} - /* * Find longest common prefix */ -static int +static size_t x_longest_prefix(int nwords, char * const * words) { - int i, j, prefix_len; + int i; + size_t j, prefix_len; char *p; if (nwords <= 0) @@ -622,7 +635,8 @@ x_free_words(int nwords, char **words) afree(words, ATEMP); } -/* Return the offset of the basename of string s (which ends at se - need not +/*- + * Return the offset of the basename of string s (which ends at se - need not * be null terminated). Trailing slashes are ignored. If s is just a slash, * then the offset is 0 (actually, length - 1). * s Return @@ -678,13 +692,14 @@ glob_table(const char *pat, XPtrV *wp, struct table *tp) static void glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) { - const char *sp, *p; + const char *sp = lpath, *p; char *xp, **words; - int staterr, pathlen, patlen, oldsize, newsize, i, j; + size_t pathlen, patlen, oldsize, newsize, i, j; XString xs; - patlen = strlen(pat) + 1; - sp = lpath; + patlen = strlen(pat); + checkoktoadd(patlen, 129 + X_EXTRA); + ++patlen; Xinit(xs, xp, patlen + 128, ATEMP); while (sp) { xp = Xstring(xs, xp); @@ -692,7 +707,8 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) p = sp + strlen(sp); pathlen = p - sp; if (pathlen) { - /* Copy sp into xp, stuffing any MAGIC characters + /* + * Copy sp into xp, stuffing any MAGIC characters * on the way */ const char *s = sp; @@ -711,15 +727,14 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) memcpy(xp, pat, patlen); oldsize = XPsize(*wp); - glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ + /* mark dirs */ + glob_str(Xstring(xs, xp), wp, 1); newsize = XPsize(*wp); /* Check that each match is executable... */ words = (char **)XPptrv(*wp); for (i = j = oldsize; i < newsize; i++) { - staterr = 0; - if ((search_access(words[i], X_OK, &staterr) >= 0) || - (staterr == EISDIR)) { + if (ksh_access(words[i], X_OK) == 0) { words[j] = words[i]; if (!(flags & XCF_FULLPATH)) memmove(words[j], words[j] + pathlen, @@ -807,7 +822,8 @@ struct x_defbindings { #define X_NTABS 3 /* normal, meta1, meta2 */ #define X_TABSZ 256 /* size of keydef tables etc */ -/* Arguments for do_complete() +/*- + * Arguments for do_complete() * 0 = enumerate M-= complete as much as possible and then list * 1 = complete M-Esc * 2 = list M-? @@ -837,7 +853,7 @@ static int x_adj_done; static int x_col; static int x_displen; static int x_arg; /* general purpose arg */ -static int x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */ +static bool x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */ static int xlp_valid; @@ -855,7 +871,7 @@ static char *killstack[KILLSIZE]; static int killsp, killtp; static int x_curprefix; #ifndef MKSH_SMALL -static char *macroptr = NULL; /* bind key macro active? */ +static char *macroptr; /* bind key macro active? */ #endif #if !MKSH_S_NOVI static int cur_col; /* current column on line */ @@ -891,7 +907,7 @@ static int x_match(char *, char *); static void x_redraw(int); static void x_push(int); static char *x_mapin(const char *, Area *) - MKSH_A_NONNULL((nonnull (1))); + MKSH_A_NONNULL((__nonnull__ (1))); static char *x_mapout(int); static void x_mapout2(int, char **); static void x_print(int, int); @@ -998,7 +1014,8 @@ static struct x_defbindings const x_defbindings[] = { { XFUNC_fold_capitalise, 1, 'C' }, { XFUNC_fold_capitalise, 1, 'c' }, #endif - /* These for ansi arrow keys: arguablely shouldn't be here by + /* + * These for ANSI arrow keys: arguablely shouldn't be here by * default, but its simpler/faster/smaller than using termcap * entries. */ @@ -1120,7 +1137,7 @@ x_emacs(char *buf, size_t len) x_nextcmd = -1; } editmode = 1; - while (1) { + while (/* CONSTCOND */ 1) { x_flush(); if ((c = x_e_getc()) < 0) return (0); @@ -1142,7 +1159,7 @@ x_emacs(char *buf, size_t len) if (!(x_ftab[f].xf_flags & XF_PREFIX) && x_last_command != XFUNC_set_arg) { x_arg = 1; - x_arg_defaulted = 1; + x_arg_defaulted = true; } i = c | (x_curprefix << 8); x_curprefix = 0; @@ -1154,7 +1171,8 @@ x_emacs(char *buf, size_t len) case KEOL: i = xep - xbuf; return (i); - case KINTR: /* special case for interrupt */ + case KINTR: + /* special case for interrupt */ trapsig(SIGINT); x_mode(false); unwind(LSHELL); @@ -1167,7 +1185,7 @@ x_emacs(char *buf, size_t len) static int x_insert(int c) { - static int left = 0, pos, save_arg; + static int left, pos, save_arg; static char str[4]; /* @@ -1265,7 +1283,8 @@ x_ins(const char *s) x_lastcp(); x_adj_ok = (xcp >= xlp); x_zots(cp); - if (adj == x_adj_done) { /* has x_adjust() been called? */ + /* has x_adjust() been called? */ + if (adj == x_adj_done) { /* no */ cp = xlp; while (cp > xcp) @@ -1354,13 +1373,15 @@ x_delete(int nc, int push) x_push(nb); xep -= nb; - memmove(xcp, xcp + nb, xep - xcp + 1); /* Copies the NUL */ - x_adj_ok = 0; /* don't redraw */ + /* Copies the NUL */ + memmove(xcp, xcp + nb, xep - xcp + 1); + /* don't redraw */ + x_adj_ok = 0; xlp_valid = false; x_zots(xcp); /* * if we are already filling the line, - * there is no need to ' ','\b'. + * there is no need to ' ', '\b'. * But if we must, make sure we do the minimum. */ if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) { @@ -1472,10 +1493,12 @@ x_goto(char *cp) /* we are heading off screen */ xcp = cp; x_adjust(); - } else if (cp < xcp) { /* move back */ + } else if (cp < xcp) { + /* move back */ while (cp < xcp) x_bs3(&xcp); - } else if (cp > xcp) { /* move forward */ + } else if (cp > xcp) { + /* move forward */ while (cp > xcp) x_zotc3(&xcp); } @@ -1515,9 +1538,11 @@ x_size2(char *cp, char **dcp) if (dcp) *dcp = cp + 1; if (c == '\t') - return (4); /* Kludge, tabs are always four spaces. */ + /* Kludge, tabs are always four spaces. */ + return (4); if (c < ' ' || c == 0x7f) - return (2); /* control unsigned char */ + /* control unsigned char */ + return (2); return (1); } @@ -1700,7 +1725,8 @@ x_next_com(int c MKSH_A_UNUSED) return (KSTD); } -/* Goto a particular history number obtained from argument. +/* + * Goto a particular history number obtained from argument. * If no argument is given history 1 is probably not what you * want so we'll simply go to the oldest one. */ @@ -1770,7 +1796,7 @@ x_search_hist(int c) unsigned char f; *p = '\0'; - while (1) { + while (/* CONSTCOND */ 1) { if (offset < 0) { x_e_puts("\nI-search: "); x_e_puts(pat); @@ -1833,7 +1859,8 @@ x_search_hist(int c) if (offset >= 0) x_load_hist(histptr + 1); break; - } else { /* other command */ + } else { + /* other command */ x_e_ungetc(c); break; } @@ -1965,7 +1992,8 @@ x_cls(int c MKSH_A_UNUSED) return (x_e_rebuildline(MKSH_CLS_STRING)); } -/* Redraw (part of) the line. If limit is < 0, the everything is redrawn +/* + * Redraw (part of) the line. If limit is < 0, the everything is redrawn * on a NEW line, otherwise limit is the screen column up to which needs * redrawing. */ @@ -2002,7 +2030,8 @@ x_redraw(int limit) limit = xx_cols; if (limit >= 0) { if (xep > xlp) - i = 0; /* we fill the line */ + /* we fill the line */ + i = 0; else { char *cpl = xbp; @@ -2019,7 +2048,8 @@ x_redraw(int limit) j++; } i = ' '; - if (xep > xlp) { /* more off screen */ + if (xep > xlp) { + /* more off screen */ if (xbp > xbuf) i = '*'; else @@ -2043,7 +2073,8 @@ x_transpose(int c MKSH_A_UNUSED) { unsigned int tmpa, tmpb; - /* What transpose is meant to do seems to be up for debate. This + /*- + * What transpose is meant to do seems to be up for debate. This * is a general summary of the options; the text is abcd with the * upper case character or underscore indicating the cursor position: * Who Before After Before After @@ -2064,8 +2095,9 @@ x_transpose(int c MKSH_A_UNUSED) x_e_putc2(7); return (KSTD); } - /* Gosling/Unipress emacs style: Swap two characters before the - * cursor, do not change cursor position + /* + * Gosling/Unipress emacs style: Swap two characters before + * the cursor, do not change cursor position */ x_bs3(&xcp); if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { @@ -2082,7 +2114,8 @@ x_transpose(int c MKSH_A_UNUSED) utf_wctomb(xcp, tmpb); x_zotc3(&xcp); } else { - /* GNU emacs style: Swap the characters before and under the + /* + * GNU emacs style: Swap the characters before and under the * cursor, move cursor position along one. */ if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { @@ -2177,7 +2210,7 @@ x_yank(int c MKSH_A_UNUSED) static int x_meta_yank(int c MKSH_A_UNUSED) { - int len; + size_t len; if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) || killstack[killtp] == 0) { @@ -2230,7 +2263,7 @@ x_vt_hack(int c) switch ((c = x_e_getc())) { case '~': x_arg = 1; - x_arg_defaulted = 1; + x_arg_defaulted = true; return (x_mv_begin(0)); case ';': /* "interesting" sequence detected */ @@ -2245,7 +2278,7 @@ x_vt_hack(int c) /*- * At this point, we have read the following octets so far: - * - ESC+[ or ESC+O or Ctrl-X (Præfix 2) + * - ESC+[ or ESC+O or Ctrl-X (Prefix 2) * - 1 (vt_hack) * - ; * - 5 (Ctrl key combiner) or 3 (Alt key combiner) @@ -2279,7 +2312,8 @@ x_mapin(const char *cp, Area *ap) /* XXX -- should handle \^ escape? */ if (*cp == '^') { cp++; - if (*cp >= '?') /* includes '?'; ASCII */ + if (*cp >= '?') + /* includes '?'; ASCII */ *op++ = CTRL(*cp); else { *op++ = '^'; @@ -2343,9 +2377,11 @@ x_print(int prefix, int key) int x_bind(const char *a1, const char *a2, #ifndef MKSH_SMALL - bool macro, /* bind -m */ + /* bind -m */ + bool macro, #endif - bool list) /* bind -l */ + /* bind -l */ + bool list) { unsigned char f; int prefix, key; @@ -2356,7 +2392,7 @@ x_bind(const char *a1, const char *a2, #endif if (x_tab == NULL) { - bi_errorf("cannot bind, not a tty"); + bi_errorf("can't bind, not a tty"); return (1); } /* List function names */ @@ -2398,16 +2434,16 @@ x_bind(const char *a1, const char *a2, && ((*m1 != '~') || *(m1 + 1)) #endif ) { - char msg[256] = "key sequence '"; + char msg[256]; const char *c = a1; - m1 = msg + strlen(msg); + m1 = msg; while (*c && m1 < (msg + sizeof(msg) - 3)) x_mapout2(*c++, &m1); - bi_errorf("%s' too long", msg); + bi_errorf("%s: %s", "too long key sequence", msg); return (1); } #ifndef MKSH_SMALL - hastilde = *m1; + hastilde = tobool(*m1); #endif afree(m2, ATEMP); @@ -2428,7 +2464,7 @@ x_bind(const char *a1, const char *a2, strcmp(x_ftab[f].xf_name, a2) == 0) break; if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) { - bi_errorf("%s: no such function", a2); + bi_errorf("%s: %s %s", a2, "no such", Tfunction); return (1); } } @@ -2466,7 +2502,7 @@ x_init_emacs(void) ainit(AEDIT); x_nextcmd = -1; - x_tab = alloc(X_NTABS * sizeof(*x_tab), AEDIT); + x_tab = alloc2(X_NTABS, sizeof(*x_tab), AEDIT); for (j = 0; j < X_TABSZ; j++) x_tab[0][j] = XFUNC_insert; for (i = 1; i < X_NTABS; i++) @@ -2477,7 +2513,7 @@ x_init_emacs(void) = x_defbindings[i].xdb_func; #ifndef MKSH_SMALL - x_atab = alloc(X_NTABS * sizeof(*x_atab), AEDIT); + x_atab = alloc2(X_NTABS, sizeof(*x_atab), AEDIT); for (i = 1; i < X_NTABS; i++) for (j = 0; j < X_TABSZ; j++) x_atab[i][j] = NULL; @@ -2603,10 +2639,10 @@ x_expand(int c MKSH_A_UNUSED) { char **words; int start, end, nwords, i; - bool is_command; - nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf, - &start, &end, &words, &is_command); + i = XCF_FILE; + nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words); if (nwords == 0) { x_e_putc2(7); @@ -2614,7 +2650,9 @@ x_expand(int c MKSH_A_UNUSED) } x_goto(xbuf + start); x_delete(end - start, false); - for (i = 0; i < nwords;) { + + i = 0; + while (i < nwords) { if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 || (++i < nwords && x_ins(" ") < 0)) { x_e_putc2(7); @@ -2626,24 +2664,27 @@ x_expand(int c MKSH_A_UNUSED) return (KSTD); } -/* type == 0 for list, 1 for complete and 2 for complete-list */ static void -do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ +do_complete( + /* XCF_{COMMAND,FILE,COMMAND_FILE} */ + int flags, + /* 0 for list, 1 for complete and 2 for complete-list */ Comp_type type) { char **words; int start, end, nlen, olen, nwords; - bool is_command, completed = false; + bool completed = false; - nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf, - &start, &end, &words, &is_command); + nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words); /* no match */ if (nwords == 0) { x_e_putc2(7); return; } if (type == CT_LIST) { - x_print_expansions(nwords, words, is_command); + x_print_expansions(nwords, words, + tobool(flags & XCF_IS_COMMAND)); x_redraw(0); x_free_words(nwords, words); return; @@ -2658,13 +2699,18 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ x_adjust(); completed = true; } - /* add space if single non-dir match */ - if (nwords == 1 && words[0][nlen - 1] != '/') { + /* + * append a space if this is a single non-directory match + * and not a parameter or homedir substitution + */ + if (nwords == 1 && words[0][nlen - 1] != '/' && + !(flags & XCF_IS_SUBGLOB)) { x_ins(" "); completed = true; } if (type == CT_COMPLIST && !completed) { - x_print_expansions(nwords, words, is_command); + x_print_expansions(nwords, words, + tobool(flags & XCF_IS_COMMAND)); completed = true; } if (completed) @@ -2673,7 +2719,8 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ x_free_words(nwords, words); } -/* NAME: +/*- + * NAME: * x_adjust - redraw the line adjusting starting point etc. * * DESCRIPTION: @@ -2689,7 +2736,8 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ static void x_adjust(void) { - x_adj_done++; /* flag the fact that we were called. */ + /* flag the fact that we were called. */ + x_adj_done++; /* * we had a problem if the prompt length > xx_cols / 2 */ @@ -2817,7 +2865,8 @@ x_e_puts(const char *s) x_e_putc3(&s); } -/* NAME: +/*- + * NAME: * x_set_arg - set an arg value for next function * * DESCRIPTION: @@ -2829,19 +2878,28 @@ x_e_puts(const char *s) static int x_set_arg(int c) { - int n = 0, first = 1; + unsigned int n = 0; + bool first = true; - c &= 255; /* strip command prefix */ - for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0) + /* strip command prefix */ + c &= 255; + while (c >= 0 && ksh_isdigit(c)) { n = n * 10 + (c - '0'); + if (n > LINE) + /* upper bound for repeat */ + goto x_set_arg_too_big; + c = x_e_getc(); + first = false; + } if (c < 0 || first) { + x_set_arg_too_big: x_e_putc2(7); x_arg = 1; - x_arg_defaulted = 1; + x_arg_defaulted = true; } else { x_e_ungetc(c); x_arg = n; - x_arg_defaulted = 0; + x_arg_defaulted = false; } return (KSTD); } @@ -2851,7 +2909,7 @@ static int x_comment(int c MKSH_A_UNUSED) { int oldsize = x_size_str(xbuf); - int len = xep - xbuf; + ssize_t len = xep - xbuf; int ret = x_do_comment(xbuf, xend - xbuf, &len); if (ret < 0) @@ -2873,7 +2931,8 @@ x_version(int c MKSH_A_UNUSED) { char *o_xbuf = xbuf, *o_xend = xend; char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp; - int vlen, lim = x_lastcp() - xbp; + int lim = x_lastcp() - xbp; + size_t vlen; char *v; strdupx(v, KSH_VERSION, ATEMP); @@ -2889,7 +2948,7 @@ x_version(int c MKSH_A_UNUSED) xbp = o_xbp; xep = o_xep; xcp = o_xcp; - x_redraw(vlen); + x_redraw((int)vlen); if (c < 0) return (KSTD); @@ -2927,7 +2986,8 @@ x_edit_line(int c MKSH_A_UNUSED) } #endif -/* NAME: +/*- + * NAME: * x_prev_histword - recover word from prev command * * DESCRIPTION: @@ -2948,11 +3008,17 @@ x_prev_histword(int c MKSH_A_UNUSED) { char *rcp, *cp; char **xhp; - int m; - - if (xmp && modified > 1) - x_kill_region(0); - m = modified ? modified : 1; + int m = 1; + /* -1 = defaulted; 0+ = argument */ + static int last_arg = -1; + + if (x_last_command == XFUNC_prev_histword) { + if (xmp && modified > 1) + x_kill_region(0); + if (modified) + m = modified; + } else + last_arg = x_arg_defaulted ? -1 : x_arg; xhp = histptr - (m - 1); if ((xhp < history) || !(cp = *xhp)) { x_e_putc2(7); @@ -2960,7 +3026,9 @@ x_prev_histword(int c MKSH_A_UNUSED) return (KSTD); } x_set_mark(0); - if (x_arg_defaulted) { + if ((x_arg = last_arg) == -1) { + /* x_arg_defaulted */ + rcp = &cp[strlen(cp) - 1]; /* * ignore white-space after the last word @@ -2973,6 +3041,7 @@ x_prev_histword(int c MKSH_A_UNUSED) rcp++; x_ins(rcp); } else { + /* not x_arg_defaulted */ char ch; rcp = cp; @@ -2981,7 +3050,7 @@ x_prev_histword(int c MKSH_A_UNUSED) */ while (*rcp && is_cfs(*rcp)) rcp++; - while (x_arg-- > 1) { + while (x_arg-- > 0) { while (*rcp && !is_cfs(*rcp)) rcp++; while (*rcp && is_cfs(*rcp)) @@ -3021,7 +3090,8 @@ x_fold_capitalise(int c MKSH_A_UNUSED) return (x_fold_case('C')); } -/* NAME: +/*- + * NAME: * x_fold_case - convert word to UPPER/lower/Capital case * * DESCRIPTION: @@ -3051,9 +3121,11 @@ x_fold_case(int c) * a different action than for the rest. */ if (cp != xep) { - if (c == 'L') /* lowercase */ + if (c == 'L') + /* lowercase */ *cp = ksh_tolower(*cp); - else /* uppercase, capitalise */ + else + /* uppercase, capitalise */ *cp = ksh_toupper(*cp); cp++; } @@ -3061,9 +3133,11 @@ x_fold_case(int c) * now for the rest of the word */ while (cp != xep && !is_mfs(*cp)) { - if (c == 'U') /* uppercase */ + if (c == 'U') + /* uppercase */ *cp = ksh_toupper(*cp); - else /* lowercase, capitalise */ + else + /* lowercase, capitalise */ *cp = ksh_tolower(*cp); cp++; } @@ -3074,7 +3148,8 @@ x_fold_case(int c) } #endif -/* NAME: +/*- + * NAME: * x_lastcp - last visible char * * SYNOPSIS: @@ -3114,44 +3189,26 @@ x_lastcp(void) return (xlp); } -static bool +static void x_mode(bool onoff) { static bool x_cur_mode; - bool prev; if (x_cur_mode == onoff) - return (x_cur_mode); - prev = x_cur_mode; + return; x_cur_mode = onoff; if (onoff) { - struct termios cb; + x_mkraw(tty_fd, NULL, false); - cb = tty_state; - - edchars.erase = cb.c_cc[VERASE]; - edchars.kill = cb.c_cc[VKILL]; - edchars.intr = cb.c_cc[VINTR]; - edchars.quit = cb.c_cc[VQUIT]; - edchars.eof = cb.c_cc[VEOF]; + edchars.erase = tty_state.c_cc[VERASE]; + edchars.kill = tty_state.c_cc[VKILL]; + edchars.intr = tty_state.c_cc[VINTR]; + edchars.quit = tty_state.c_cc[VQUIT]; + edchars.eof = tty_state.c_cc[VEOF]; #ifdef VWERASE - edchars.werase = cb.c_cc[VWERASE]; -#endif - cb.c_iflag &= ~(INLCR | ICRNL); - cb.c_lflag &= ~(ISIG | ICANON | ECHO); -#if defined(VLNEXT) && defined(_POSIX_VDISABLE) - /* osf/1 processes lnext when ~icanon */ - cb.c_cc[VLNEXT] = _POSIX_VDISABLE; + edchars.werase = tty_state.c_cc[VWERASE]; #endif - /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ -#if defined(VDISCARD) && defined(_POSIX_VDISABLE) - cb.c_cc[VDISCARD] = _POSIX_VDISABLE; -#endif - cb.c_cc[VTIME] = 0; - cb.c_cc[VMIN] = 1; - - tcsetattr(tty_fd, TCSADRAIN, &cb); #ifdef _POSIX_VDISABLE /* Convert unset values to internal 'unset' value */ @@ -3183,8 +3240,6 @@ x_mode(bool onoff) bind_if_not_bound(0, edchars.quit, XFUNC_noop); } else tcsetattr(tty_fd, TCSADRAIN, &tty_state); - - return (prev); } #if !MKSH_S_NOVI @@ -3194,10 +3249,10 @@ x_mode(bool onoff) struct edstate { char *cbuf; - int winleft; - int cbufsize; - int linelen; - int cursor; + ssize_t winleft; + ssize_t cbufsize; + ssize_t linelen; + ssize_t cursor; }; static int vi_hook(int); @@ -3210,7 +3265,7 @@ static void yank_range(int, int); static int bracktype(int); static void save_cbuf(void); static void restore_cbuf(void); -static int putbuf(const char *, int, int); +static int putbuf(const char *, ssize_t, int); static void del_range(int, int); static int findch(int, int, int, int); static int forwword(int); @@ -3221,7 +3276,7 @@ static int Backword(int); static int Endword(int); static int grabhist(int, int); static int grabsearch(int, int, int, char *); -static void redraw_line(int); +static void redraw_line(bool); static void refresh(int); static int outofwin(void); static void rewindow(void); @@ -3237,58 +3292,58 @@ static void vi_error(void); static void vi_macro_reset(void); static int x_vi_putbuf(const char *, size_t); -#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */ -#define M_ 0x2 /* movement command (h, l, etc.) */ -#define E_ 0x4 /* extended command (c, d, y) */ -#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */ -#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */ -#define B_ 0x20 /* bad command (^@) */ -#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */ -#define S_ 0x80 /* search (/, ?) */ - -#define is_bad(c) (classify[(c)&0x7f]&B_) -#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_)) -#define is_move(c) (classify[(c)&0x7f]&M_) -#define is_extend(c) (classify[(c)&0x7f]&E_) -#define is_long(c) (classify[(c)&0x7f]&X_) -#define is_undoable(c) (!(classify[(c)&0x7f]&U_)) -#define is_srch(c) (classify[(c)&0x7f]&S_) -#define is_zerocount(c) (classify[(c)&0x7f]&Z_) +#define vC 0x01 /* a valid command that isn't a vM, vE, vU */ +#define vM 0x02 /* movement command (h, l, etc.) */ +#define vE 0x04 /* extended command (c, d, y) */ +#define vX 0x08 /* long command (@, f, F, t, T, etc.) */ +#define vU 0x10 /* an UN-undoable command (that isn't a vM) */ +#define vB 0x20 /* bad command (^@) */ +#define vZ 0x40 /* repeat count defaults to 0 (not 1) */ +#define vS 0x80 /* search (/, ?) */ + +#define is_bad(c) (classify[(c)&0x7f]&vB) +#define is_cmd(c) (classify[(c)&0x7f]&(vM|vE|vC|vU)) +#define is_move(c) (classify[(c)&0x7f]&vM) +#define is_extend(c) (classify[(c)&0x7f]&vE) +#define is_long(c) (classify[(c)&0x7f]&vX) +#define is_undoable(c) (!(classify[(c)&0x7f]&vU)) +#define is_srch(c) (classify[(c)&0x7f]&vS) +#define is_zerocount(c) (classify[(c)&0x7f]&vZ) static const unsigned char classify[128] = { /* 0 1 2 3 4 5 6 7 */ /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */ - B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0, + vB, 0, 0, 0, 0, vC|vU, vC|vZ, 0, /* 1 ^H ^I ^J ^K ^L ^M ^N ^O */ - M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0, + vM, vC|vZ, 0, 0, vC|vU, 0, vC, 0, /* 2 ^P ^Q ^R ^S ^T ^U ^V ^W */ - C_, 0, C_|U_, 0, 0, 0, C_, 0, + vC, 0, vC|vU, 0, 0, 0, vC, 0, /* 3 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ - C_, 0, 0, C_|Z_, 0, 0, 0, 0, + vC, 0, 0, vC|vZ, 0, 0, 0, 0, /* 4 <space> ! " # $ % & ' */ - M_, 0, 0, C_, M_, M_, 0, 0, + vM, 0, 0, vC, vM, vM, 0, 0, /* 5 ( ) * + , - . / */ - 0, 0, C_, C_, M_, C_, 0, C_|S_, + 0, 0, vC, vC, vM, vC, 0, vC|vS, /* 6 0 1 2 3 4 5 6 7 */ - M_, 0, 0, 0, 0, 0, 0, 0, + vM, 0, 0, 0, 0, 0, 0, 0, /* 7 8 9 : ; < = > ? */ - 0, 0, 0, M_, 0, C_, 0, C_|S_, + 0, 0, 0, vM, 0, vC, 0, vC|vS, /* 8 @ A B C D E F G */ - C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_, + vC|vX, vC, vM, vC, vC, vM, vM|vX, vC|vU|vZ, /* 9 H I J K L M N O */ - 0, C_, 0, 0, 0, 0, C_|U_, 0, + 0, vC, 0, 0, 0, 0, vC|vU, 0, /* A P Q R S T U V W */ - C_, 0, C_, C_, M_|X_, C_, 0, M_, + vC, 0, vC, vC, vM|vX, vC, 0, vM, /* B X Y Z [ \ ] ^ _ */ - C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_, + vC, vC|vU, 0, 0, vC|vZ, 0, vM, vC|vZ, /* C ` a b c d e f g */ - 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_, + 0, vC, vM, vE, vE, vM, vM|vX, vC|vZ, /* D h i j k l m n o */ - M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0, + vM, vC, vC|vU, vC|vU, vM, 0, vC|vU, 0, /* E p q r s t u v w */ - C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_, M_, + vC, 0, vX, vC, vM|vX, vC|vU, vC|vU|vZ, vM, /* F x y z { | } ~ ^? */ - C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0 + vC, vE|vU, 0, 0, vM|vZ, 0, vC, 0 }; #define MAXVICMD 3 @@ -3340,7 +3395,8 @@ static int ohnum; /* history line copied (after mod) */ static int hlast; /* 1 past last position in history */ static int state; -/* Information for keeping track of macros that are being expanded. +/* + * Information for keeping track of macros that are being expanded. * The format of buf is the alias contents followed by a NUL byte followed * by the name (letter) of the alias. The end of the buffer is marked by * a double NUL. The name of the alias is stored so recursive macros can @@ -3349,14 +3405,14 @@ static int state; struct macro_state { unsigned char *p; /* current position in buf */ unsigned char *buf; /* pointer to macro(s) being expanded */ - int len; /* how much data in buffer */ + size_t len; /* how much data in buffer */ }; static struct macro_state macro; -enum expand_mode { - NONE, EXPAND, COMPLETE, PRINT -}; -static enum expand_mode expanded = NONE; /* last input was expanded */ +/* last input was expanded */ +static enum expand_mode { + NONE = 0, EXPAND, COMPLETE, PRINT +} expanded; static int x_vi(char *buf, size_t len) @@ -3397,8 +3453,10 @@ x_vi(char *buf, size_t len) wbuf[0] = aresize(wbuf[0], wbuf_len, APERM); wbuf[1] = aresize(wbuf[1], wbuf_len, APERM); } - (void)memset(wbuf[0], ' ', wbuf_len); - (void)memset(wbuf[1], ' ', wbuf_len); + if (wbuf_len) { + memset(wbuf[0], ' ', wbuf_len); + memset(wbuf[1], ' ', wbuf_len); + } winwidth = x_cols - pwidth - 3; win = 0; morec = ' '; @@ -3407,7 +3465,7 @@ x_vi(char *buf, size_t len) editmode = 2; x_flush(); - while (1) { + while (/* CONSTCOND */ 1) { if (macro.p) { c = *macro.p++; /* end of current macro? */ @@ -3804,7 +3862,8 @@ vi_insert(int ch) expanded = NONE; return (0); } - /* If any chars are entered before escape, trash the saved insert + /* + * If any chars are entered before escape, trash the saved insert * buffer (if user inserts & deletes char, ibuf gets trashed and * we don't want to use it) */ @@ -3905,14 +3964,14 @@ vi_cmd(int argcnt, const char *cmd) case Ctrl('l'): case Ctrl('r'): - redraw_line(1); + redraw_line(true); break; case '@': { static char alias[] = "_\0"; struct tbl *ap; - int olen, nlen; + size_t olen, nlen; char *p, *nbuf; /* lookup letter in alias list... */ @@ -3929,6 +3988,10 @@ vi_cmd(int argcnt, const char *cmd) nlen = strlen(ap->val.s) + 1; olen = !macro.p ? 2 : macro.len - (macro.p - macro.buf); + /* + * at this point, it's fairly reasonable that + * nlen + olen + 2 doesn't overflow + */ nbuf = alloc(nlen + 1 + olen, APERM); memcpy(nbuf, ap->val.s, nlen); nbuf[nlen++] = cmd[1]; @@ -4323,29 +4386,37 @@ vi_cmd(int argcnt, const char *cmd) return (ret); } - case '=': /* AT&T ksh */ - case Ctrl('e'): /* Nonstandard vi/ksh */ + /* AT&T ksh */ + case '=': + /* Nonstandard vi/ksh */ + case Ctrl('e'): print_expansions(es, 1); break; - case Ctrl('i'): /* Nonstandard vi/ksh */ + /* Nonstandard vi/ksh */ + case Ctrl('i'): if (!Flag(FVITABCOMPLETE)) return (-1); complete_word(1, argcnt); break; - case Ctrl('['): /* some annoying AT&T kshs */ + /* some annoying AT&T kshs */ + case Ctrl('['): if (!Flag(FVIESCCOMPLETE)) return (-1); - case '\\': /* AT&T ksh */ - case Ctrl('f'): /* Nonstandard vi/ksh */ + /* AT&T ksh */ + case '\\': + /* Nonstandard vi/ksh */ + case Ctrl('f'): complete_word(1, argcnt); break; - case '*': /* AT&T ksh */ - case Ctrl('x'): /* Nonstandard vi/ksh */ + /* AT&T ksh */ + case '*': + /* Nonstandard vi/ksh */ + case Ctrl('x'): expand_word(1); break; } @@ -4613,7 +4684,7 @@ x_vi_putbuf(const char *s, size_t len) } static int -putbuf(const char *buf, int len, int repl) +putbuf(const char *buf, ssize_t len, int repl) { if (len == 0) return (0); @@ -4811,7 +4882,7 @@ grabhist(int save, int n) } (void)histnum(n); if ((hptr = *histpos()) == NULL) { - internal_warningf("grabhist: bad history array"); + internal_warningf("%s: %s", "grabhist", "bad history array"); return (-1); } if (save) @@ -4839,7 +4910,7 @@ grabsearch(int save, int start, int fwd, char *pat) start--; anchored = *pat == '^' ? (++pat, 1) : 0; if ((hist = findhist(start, fwd, pat, anchored)) < 0) { - /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */ + /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) {} */ /* XXX should strcmp be strncmp? */ if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) { restore_cbuf(); @@ -4859,9 +4930,10 @@ grabsearch(int save, int start, int fwd, char *pat) } static void -redraw_line(int newl) +redraw_line(bool newl) { - (void)memset(wbuf[win], ' ', wbuf_len); + if (wbuf_len) + memset(wbuf[win], ' ', wbuf_len); if (newl) { x_putc('\r'); x_putc('\n'); @@ -4996,7 +5068,8 @@ display(char *wb1, char *wb2, int leftside) col++; } if (es->winleft > 0 && moreright) - /* POSIX says to use * for this but that is a globbing + /* + * POSIX says to use * for this but that is a globbing * character and may confuse people; + is more innocuous */ mc = '+'; @@ -5045,11 +5118,8 @@ static int expand_word(int cmd) { static struct edstate *buf; - int rval = 0; - int nwords; - int start, end; + int rval = 0, nwords, start, end, i; char **words; - int i; /* Undo previous expansion */ if (cmd == 0 && expanded == EXPAND && buf) { @@ -5063,9 +5133,9 @@ expand_word(int cmd) buf = 0; } - nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH, - es->cbuf, es->linelen, es->cursor, - &start, &end, &words, NULL); + i = XCF_COMMAND_FILE | XCF_FULLPATH; + nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor, + &start, &end, &words); if (nwords == 0) { vi_error(); return (-1); @@ -5075,7 +5145,8 @@ expand_word(int cmd) expanded = EXPAND; del_range(start, end); es->cursor = start; - for (i = 0; i < nwords; ) { + i = 0; + while (i < nwords) { if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) { rval = -1; break; @@ -5100,10 +5171,11 @@ static int complete_word(int cmd, int count) { static struct edstate *buf; - int rval, nwords, start, end, match_len; + int rval, nwords, start, end, flags; + size_t match_len; char **words; char *match; - bool is_command, is_unique; + bool is_unique; /* Undo previous completion */ if (cmd == 0 && expanded == COMPLETE && buf) { @@ -5122,12 +5194,15 @@ complete_word(int cmd, int count) buf = 0; } - /* XCF_FULLPATH for count 'cause the menu printed by print_expansions() - * was done this way. + /* + * XCF_FULLPATH for count 'cause the menu printed by + * print_expansions() was done this way. */ - nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0), - es->cbuf, es->linelen, es->cursor, - &start, &end, &words, &is_command); + flags = XCF_COMMAND_FILE; + if (count) + flags |= XCF_FULLPATH; + nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor, + &start, &end, &words); if (nwords == 0) { vi_error(); return (-1); @@ -5138,15 +5213,16 @@ complete_word(int cmd, int count) count--; if (count >= nwords) { vi_error(); - x_print_expansions(nwords, words, is_command); + x_print_expansions(nwords, words, + tobool(flags & XCF_IS_COMMAND)); x_free_words(nwords, words); - redraw_line(0); + redraw_line(false); return (-1); } /* * Expand the count'th word to its basename */ - if (is_command) { + if (flags & XCF_IS_COMMAND) { match = words[count] + x_basename(words[count], NULL); /* If more than one possible match, use full path */ @@ -5165,7 +5241,8 @@ complete_word(int cmd, int count) } else { match = words[0]; match_len = x_longest_prefix(nwords, words); - expanded = COMPLETE; /* next call will list completions */ + /* next call will list completions */ + expanded = COMPLETE; is_unique = nwords == 1; } @@ -5173,18 +5250,25 @@ complete_word(int cmd, int count) del_range(start, end); es->cursor = start; - /* escape all shell-sensitive characters and put the result into - * command buffer */ + /* + * escape all shell-sensitive characters and put the result into + * command buffer + */ rval = x_escape(match, match_len, x_vi_putbuf); if (rval == 0 && is_unique) { - /* If exact match, don't undo. Allows directory completions + /* + * If exact match, don't undo. Allows directory completions * to be used (ie, complete the next portion of the path). */ expanded = NONE; - /* If not a directory, add a space to the end... */ - if (match_len > 0 && match[match_len - 1] != '/') + /* + * append a space if this is a non-directory match + * and not a parameter or homedir substitution + */ + if (match_len > 0 && match[match_len - 1] != '/' && + !(flags & XCF_IS_SUBGLOB)) rval = putbuf(" ", 1, 0); } x_free_words(nwords, words); @@ -5192,7 +5276,8 @@ complete_word(int cmd, int count) modified = 1; hnum = hlast; insert = INSERT; - lastac = 0; /* prevent this from being redone... */ + /* prevent this from being redone... */ + lastac = 0; refresh(0); return (rval); @@ -5201,20 +5286,19 @@ complete_word(int cmd, int count) static int print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED) { - int start, end, nwords; + int start, end, nwords, i; char **words; - bool is_command; - nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH, - est->cbuf, est->linelen, est->cursor, - &start, &end, &words, &is_command); + i = XCF_COMMAND_FILE | XCF_FULLPATH; + nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor, + &start, &end, &words); if (nwords == 0) { vi_error(); return (-1); } - x_print_expansions(nwords, words, is_command); + x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND)); x_free_words(nwords, words); - redraw_line(0); + redraw_line(false); return (0); } @@ -5247,3 +5331,34 @@ vi_macro_reset(void) } } #endif /* !MKSH_S_NOVI */ + +void +x_mkraw(int fd, struct termios *ocb, bool forread) +{ + struct termios cb; + + if (ocb) + tcgetattr(fd, ocb); + else + ocb = &tty_state; + + cb = *ocb; + if (forread) { + cb.c_lflag &= ~(ICANON) | ECHO; + } else { + cb.c_iflag &= ~(INLCR | ICRNL); + cb.c_lflag &= ~(ISIG | ICANON | ECHO); + } +#if defined(VLNEXT) && defined(_POSIX_VDISABLE) + /* OSF/1 processes lnext when ~icanon */ + cb.c_cc[VLNEXT] = _POSIX_VDISABLE; +#endif + /* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */ +#if defined(VDISCARD) && defined(_POSIX_VDISABLE) + cb.c_cc[VDISCARD] = _POSIX_VDISABLE; +#endif + cb.c_cc[VTIME] = 0; + cb.c_cc[VMIN] = 1; + + tcsetattr(fd, TCSADRAIN, &cb); +} @@ -1,7 +1,7 @@ -/* $OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $ */ +/* $OpenBSD: eval.c,v 1.37 2011/10/11 14:32:43 otto Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.109 2011/10/11 19:06:07 tg Exp $"); /* * string expansion @@ -67,11 +67,11 @@ static char *tilde(char *); static char *homedir(char *); #endif static void alt_expand(XPtrV *, char *, char *, char *, int); -static size_t utflen(const char *); +static int utflen(const char *); static void utfincptr(const char *, mksh_ari_t *); /* UTFMODE functions */ -static size_t +static int utflen(const char *s) { size_t n; @@ -84,7 +84,10 @@ utflen(const char *s) } } else n = strlen(s); - return (n); + + if (n > 2147483647) + n = 2147483647; + return ((int)n); } static void @@ -108,7 +111,7 @@ substitute(const char *cp, int f) s->start = s->str = cp; source = s; if (yylex(ONEWORD) != LWORD) - internal_errorf("substitute"); + internal_errorf("bad substitution"); source = sold; afree(s, ATEMP); return (evalstr(yylval.cp, f)); @@ -129,7 +132,8 @@ eval(const char **ap, int f) return (vap.rw); } XPinit(w, 32); - XPput(w, NULL); /* space for shell name */ + /* space for shell name */ + XPput(w, NULL); while (*ap != NULL) expand(*ap++, &w, f); XPput(w, NULL); @@ -205,11 +209,13 @@ expand(const char *cp, /* input word */ const char *sp; /* source */ int fdo, word; /* second pass flags; have word */ int doblank; /* field splitting of parameter/command subst */ - Expand x = { /* expansion variables */ + Expand x = { + /* expansion variables */ NULL, { NULL }, NULL, 0 }; SubType st_head, *st; - int newlines = 0; /* For trailing newlines in COMSUB */ + /* For trailing newlines in COMSUB */ + int newlines = 0; int saw_eq, tilde_ok; int make_magic; size_t len; @@ -226,14 +232,16 @@ expand(const char *cp, /* input word */ if (Flag(FMARKDIRS)) f |= DOMARKDIRS; if (Flag(FBRACEEXPAND) && (f & DOGLOB)) - f |= DOBRACE_; + f |= DOBRACE; - Xinit(ds, dp, 128, ATEMP); /* init dest. string */ + /* init destination string */ + Xinit(ds, dp, 128, ATEMP); type = XBASE; sp = cp; fdo = 0; saw_eq = 0; - tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */ + /* must be 1/0 */ + tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; doblank = 0; make_magic = 0; word = (f&DOBLANK) ? IFS_WS : IFS_WORD; @@ -241,11 +249,12 @@ expand(const char *cp, /* input word */ memset(&st_head, 0, sizeof(st_head)); st = &st_head; - while (1) { + while (/* CONSTCOND */ 1) { Xcheck(ds, dp); switch (type) { - case XBASE: /* original prefixed string */ + case XBASE: + /* original prefixed string */ c = *sp++; switch (c) { case EOS: @@ -255,7 +264,8 @@ expand(const char *cp, /* input word */ c = *sp++; break; case QCHAR: - quote |= 2; /* temporary quote */ + /* temporary quote */ + quote |= 2; c = *sp++; break; case OQUOTE: @@ -299,7 +309,8 @@ expand(const char *cp, /* input word */ char *p; v.flag = DEFINED|ISSET|INTEGER; - v.type = 10; /* not default */ + /* not default */ + v.type = 10; v.name[0] = '\0'; v_evaluate(&v, substitute(sp, 0), KSH_UNWIND_ERROR, true); @@ -310,23 +321,27 @@ expand(const char *cp, /* input word */ } } continue; - case OSUBST: { /* ${{#}var{:}[=+-?#%]word} */ - /* format is: + case OSUBST: { + /* ${{#}var{:}[=+-?#%]word} */ + /*- + * format is: * OSUBST [{x] plain-variable-part \0 * compiled-word-part CSUBST [}x] * This is where all syntax checking gets done... */ - const char *varname = ++sp; /* skip the { or x (}) */ + /* skip the { or x (}) */ + const char *varname = ++sp; int stype; int slen = 0; - sp = cstrchr(sp, '\0') + 1; /* skip variable */ + /* skip variable */ + sp = cstrchr(sp, '\0') + 1; type = varsub(&x, varname, sp, &stype, &slen); if (type < 0) { char *beg, *end, *str; - unwind_substsyn: - sp = varname - 2; /* restore sp */ + /* restore sp */ + sp = varname - 2; end = (beg = wdcopy(sp, ATEMP)) + (wdscan(sp, CSUBST) - sp); /* ({) the } or x is already skipped */ @@ -334,12 +349,13 @@ expand(const char *cp, /* input word */ *end = EOS; str = snptreef(NULL, 64, "%S", beg); afree(beg, ATEMP); - errorf("%s: bad substitution", str); + errorf("%s: %s", str, "bad substitution"); } if (f & DOBLANK) doblank++; tilde_ok = 0; - if (type == XBASE) { /* expand? */ + if (type == XBASE) { + /* expand? */ if (!st->next) { SubType *newst; @@ -357,7 +373,11 @@ expand(const char *cp, /* input word */ /* skip qualifier(s) */ if (stype) sp += slen; - switch (stype & 0x7f) { + switch (stype & 0x17F) { + case 0x100 | '#': + x.str = shf_smprintf("%08X", + (unsigned int)hash(str_val(st->var))); + break; case '0': { char *beg, *mid, *end, *stg; mksh_ari_t from = 0, num = -1, flen, finc = 0; @@ -374,16 +394,18 @@ expand(const char *cp, /* input word */ } else { end = mid + (wdscan(mid, ADELIM) - mid); - if (end >= stg) + if (end >= stg || + /* more than max delimiters */ + end[-1] != /*{*/ '}') goto unwind_substsyn; end[-2] = EOS; sp += end - beg - 1; } - evaluate(substitute(stg = wdstrip(beg, false, false), 0), + evaluate(substitute(stg = wdstrip(beg, 0), 0), &from, KSH_UNWIND_ERROR, true); afree(stg, ATEMP); if (end) { - evaluate(substitute(stg = wdstrip(mid, false, false), 0), + evaluate(substitute(stg = wdstrip(mid, 0), 0), &num, KSH_UNWIND_ERROR, true); afree(stg, ATEMP); } @@ -422,10 +444,11 @@ expand(const char *cp, /* input word */ else d[-2] = EOS; sp += (d ? d : p) - s - 1; - tpat0 = wdstrip(s, true, true); + tpat0 = wdstrip(s, + WDS_KEEPQ | WDS_MAGIC); pat = substitute(tpat0, 0); if (d) { - d = wdstrip(p, true, false); + d = wdstrip(p, WDS_KEEPQ); rrep = substitute(d, 0); afree(d, ATEMP); } else @@ -445,12 +468,44 @@ expand(const char *cp, /* input word */ *d = '\0'; afree(tpat0, ATEMP); - /* reject empty pattern */ - if (!*pat || gmatchx("", pat, false)) + /* check for special cases */ + d = str_val(st->var); + switch (*pat) { + case '#': + /* anchor at begin */ + tpat0 = pat + 1; + tpat1 = rrep; + tpat2 = d; + break; + case '%': + /* anchor at end */ + tpat0 = pat + 1; + tpat1 = d; + tpat2 = rrep; + break; + case '\0': + /* empty pattern */ goto no_repl; + default: + tpat0 = pat; + /* silence gcc */ + tpat1 = tpat2 = NULL; + } + if (gmatchx(null, tpat0, false)) { + /* + * pattern matches + * the empty string + */ + if (tpat0 == pat) + goto no_repl; + /* but is anchored */ + s = shf_smprintf("%s%s", + tpat1, tpat2); + goto do_repl; + } /* prepare string on which to work */ - strdupx(s, str_val(st->var), ATEMP); + strdupx(s, d, ATEMP); sbeg = s; /* first see if we have any match at all */ @@ -469,7 +524,8 @@ expand(const char *cp, /* input word */ tpat2 = tpat1 + 2; } again_repl: - /* this would not be necessary if gmatchx would return + /* + * this would not be necessary if gmatchx would return * the start and end values of a match found, like re* */ if (!gmatchx(sbeg, tpat1, false)) @@ -489,8 +545,9 @@ expand(const char *cp, /* input word */ while (p >= sbeg) { bool gotmatch; - c = *p; *p = '\0'; - gotmatch = gmatchx(sbeg, tpat0, false); + c = *p; + *p = '\0'; + gotmatch = tobool(gmatchx(sbeg, tpat0, false)); *p = c; if (gotmatch) break; @@ -506,6 +563,7 @@ expand(const char *cp, /* input word */ goto again_repl; end_repl: afree(tpat1, ATEMP); + do_repl: x.str = s; no_repl: afree(pat, ATEMP); @@ -515,19 +573,23 @@ expand(const char *cp, /* input word */ } case '#': case '%': - /* ! DOBLANK,DOBRACE_,DOTILDE */ + /* ! DOBLANK,DOBRACE,DOTILDE */ f = DOPAT | (f&DONTRUNCOMMAND) | - DOTEMP_; + DOTEMP; st->quotew = quote = 0; - /* Prepend open pattern (so | + /* + * Prepend open pattern (so | * in a trim will work as * expected) */ - *dp++ = MAGIC; - *dp++ = (char)('@' | 0x80); + if (!Flag(FSH)) { + *dp++ = MAGIC; + *dp++ = '@' | 0x80; + } break; case '=': - /* Enabling tilde expansion + /* + * Enabling tilde expansion * after :s here is * non-standard ksh, but is * consistent with rules for @@ -542,16 +604,17 @@ expand(const char *cp, /* input word */ */ if (!(x.var->flag & INTEGER)) f |= DOASNTILDE|DOTILDE; - f |= DOTEMP_; - /* These will be done after the + f |= DOTEMP; + /* + * These will be done after the * value has been assigned. */ - f &= ~(DOBLANK|DOGLOB|DOBRACE_); + f &= ~(DOBLANK|DOGLOB|DOBRACE); tilde_ok = 1; break; case '?': f &= ~DOBLANK; - f |= DOTEMP_; + f |= DOTEMP; /* FALLTHROUGH */ default: /* Enable tilde expansion */ @@ -563,22 +626,30 @@ expand(const char *cp, /* input word */ sp += wdscan(sp, CSUBST) - sp; continue; } - case CSUBST: /* only get here if expanding word */ + case CSUBST: + /* only get here if expanding word */ do_CSUBST: - sp++; /* ({) skip the } or x */ - tilde_ok = 0; /* in case of ${unset:-} */ + /* ({) skip the } or x */ + sp++; + /* in case of ${unset:-} */ + tilde_ok = 0; *dp = '\0'; quote = st->quotep; f = st->f; if (f&DOBLANK) doblank--; - switch (st->stype&0x7f) { + switch (st->stype & 0x17F) { case '#': case '%': - /* Append end-pattern */ - *dp++ = MAGIC; *dp++ = ')'; *dp = '\0'; + if (!Flag(FSH)) { + /* Append end-pattern */ + *dp++ = MAGIC; + *dp++ = ')'; + } + *dp = '\0'; dp = Xrestpos(ds, dp, st->base); - /* Must use st->var since calling + /* + * Must use st->var since calling * global would break things * like x[i+=1]. */ @@ -593,20 +664,24 @@ expand(const char *cp, /* input word */ st = st->prev; continue; case '=': - /* Restore our position and substitute + /* + * Restore our position and substitute * the value of st->var (may not be * the assigned value in the presence * of integer/right-adj/etc attributes). */ dp = Xrestpos(ds, dp, st->base); - /* Must use st->var since calling + /* + * Must use st->var since calling * global would cause with things * like x[i+=1] to be evaluated twice. */ - /* Note: not exported by FEXPORT + /* + * Note: not exported by FEXPORT * in AT&T ksh. */ - /* XXX POSIX says readonly is only + /* + * XXX POSIX says readonly is only * fatal for special builtins (setstr * does readonly check). */ @@ -630,6 +705,7 @@ expand(const char *cp, /* input word */ } case '0': case '/': + case 0x100 | '#': dp = Xrestpos(ds, dp, st->base); type = XSUB; if (f&DOBLANK) @@ -641,18 +717,21 @@ expand(const char *cp, /* input word */ type = XBASE; continue; - case OPAT: /* open pattern: *(foo|bar) */ + case OPAT: + /* open pattern: *(foo|bar) */ /* Next char is the type of pattern */ make_magic = 1; - c = *sp++ + 0x80; + c = *sp++ | 0x80; break; - case SPAT: /* pattern separator (|) */ + case SPAT: + /* pattern separator (|) */ make_magic = 1; c = '|'; break; - case CPAT: /* close pattern */ + case CPAT: + /* close pattern */ make_magic = 1; c = /*(*/ ')'; break; @@ -660,14 +739,16 @@ expand(const char *cp, /* input word */ break; case XNULLSUB: - /* Special case for "$@" (and "${foo[@]}") - no + /* + * Special case for "$@" (and "${foo[@]}") - no * word is generated if $# is 0 (unless there is * other stuff inside the quotes). */ type = XBASE; if (f&DOBLANK) { doblank--; - /* not really correct: x=; "$x$@" should + /* + * not really correct: x=; "$x$@" should * generate a null argument and * set A; "${@:+}" shouldn't. */ @@ -691,7 +772,8 @@ expand(const char *cp, /* input word */ quote = 1; case XARG: if ((c = *x.str++) == '\0') { - /* force null words to be created so + /* + * force null words to be created so * set -- '' 2 ''; foo "$@" will do * the right thing */ @@ -718,7 +800,8 @@ expand(const char *cp, /* input word */ break; case XCOM: - if (newlines) { /* Spit out saved NLs */ + if (newlines) { + /* Spit out saved NLs */ c = '\n'; --newlines; } else { @@ -748,7 +831,8 @@ expand(const char *cp, /* input word */ /* check for end of word or IFS separation */ if (c == 0 || (!quote && (f & DOBLANK) && doblank && !make_magic && ctype(c, C_IFS))) { - /* How words are broken up: + /*- + * How words are broken up: * | value of c * word | ws nws 0 * ----------------------------------- @@ -765,14 +849,14 @@ expand(const char *cp, /* input word */ *dp++ = '\0'; p = Xclose(ds, dp); - if (fdo & DOBRACE_) + if (fdo & DOBRACE) /* also does globbing */ alt_expand(wp, p, p, p + Xlength(ds, (dp - 1)), fdo | (f & DOMARKDIRS)); else if (fdo & DOGLOB) glob(p, wp, f & DOMARKDIRS); - else if ((f & DOPAT) || !(fdo & DOMAGIC_)) + else if ((f & DOPAT) || !(fdo & DOMAGIC)) XPput(*wp, p); else XPput(*wp, debunk(p, p, strlen(p) + 1)); @@ -807,12 +891,13 @@ expand(const char *cp, /* input word */ case NOT: case '-': case ']': - /* For character classes - doesn't hurt + /* + * For character classes - doesn't hurt * to have magic !,-,]s outside of * [...] expressions. */ if (f & (DOPAT | DOGLOB)) { - fdo |= DOMAGIC_; + fdo |= DOMAGIC; if (c == '[') fdo |= f & DOGLOB; *dp++ = MAGIC; @@ -821,35 +906,37 @@ expand(const char *cp, /* input word */ case '*': case '?': if (f & (DOPAT | DOGLOB)) { - fdo |= DOMAGIC_ | (f & DOGLOB); + fdo |= DOMAGIC | (f & DOGLOB); *dp++ = MAGIC; } break; case OBRACE: case ',': case CBRACE: - if ((f & DOBRACE_) && (c == OBRACE || - (fdo & DOBRACE_))) { - fdo |= DOBRACE_|DOMAGIC_; + if ((f & DOBRACE) && (c == OBRACE || + (fdo & DOBRACE))) { + fdo |= DOBRACE|DOMAGIC; *dp++ = MAGIC; } break; case '=': /* Note first unquoted = for ~ */ - if (!(f & DOTEMP_) && !saw_eq && + if (!(f & DOTEMP) && !saw_eq && (Flag(FBRACEEXPAND) || (f & DOASNTILDE))) { saw_eq = 1; tilde_ok = 1; } break; - case ':': /* : */ + case ':': + /* : */ /* Note unquoted : for ~ */ - if (!(f & DOTEMP_) && (f & DOASNTILDE)) + if (!(f & DOTEMP) && (f & DOASNTILDE)) tilde_ok = 1; break; case '~': - /* tilde_ok is reset whenever + /* + * tilde_ok is reset whenever * any of ' " $( $(( ${ } are seen. * Note that tilde_ok must be preserved * through the sequence ${A=a=}~ @@ -875,17 +962,19 @@ expand(const char *cp, /* input word */ break; } else - quote &= ~2; /* undo temporary */ + /* undo temporary */ + quote &= ~2; if (make_magic) { make_magic = 0; - fdo |= DOMAGIC_ | (f & DOGLOB); + fdo |= DOMAGIC | (f & DOGLOB); *dp++ = MAGIC; } else if (ISMAGIC(c)) { - fdo |= DOMAGIC_; + fdo |= DOMAGIC; *dp++ = MAGIC; } - *dp++ = c; /* save output char */ + /* save output char */ + *dp++ = c; word = IFS_WORD; } } @@ -907,7 +996,8 @@ varsub(Expand *xp, const char *sp, const char *word, struct tbl *vp; bool zero_ok = false; - if ((stype = sp[0]) == '\0') /* Bad variable name */ + if ((stype = sp[0]) == '\0') + /* Bad variable name */ return (-1); xp->var = NULL; @@ -974,8 +1064,9 @@ varsub(Expand *xp, const char *sp, const char *word, } } if (Flag(FNOUNSET) && c == 0 && !zero_ok) - errorf("%s: parameter not set", sp); - *stypep = 0; /* unqualified variable/string substitution */ + errorf("%s: %s", sp, "parameter not set"); + /* unqualified variable/string substitution */ + *stypep = 0; xp->str = shf_smprintf("%d", c); return (XSUB); } @@ -1000,14 +1091,24 @@ varsub(Expand *xp, const char *sp, const char *word, } else if (ctype(c, C_SUBOP1)) { slen += 2; stype |= c; - } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */ + } else if (ctype(c, C_SUBOP2)) { + /* Note: ksh88 allows :%, :%%, etc */ slen += 2; stype = c; if (word[slen + 0] == CHAR && c == word[slen + 1]) { stype |= 0x80; slen += 2; } - } else if (stype) /* : is not ok */ + } else if (c == '@') { + /* @x where x is command char */ + slen += 2; + stype |= 0x100; + if (word[slen] == CHAR) { + stype |= word[slen + 1]; + slen += 2; + } + } else if (stype) + /* : is not ok */ return (-1); if (!stype && *word != CSUBST) return (-1); @@ -1016,12 +1117,13 @@ varsub(Expand *xp, const char *sp, const char *word, c = sp[0]; if (c == '*' || c == '@') { - switch (stype & 0x7f) { + switch (stype & 0x17F) { case '=': /* can't assign to a vector */ case '%': /* can't trim a vector (yet) */ case '#': case '0': case '/': + case 0x100 | '#': return (-1); } if (e->loc->argc == 0) { @@ -1034,19 +1136,21 @@ varsub(Expand *xp, const char *sp, const char *word, xp->split = c == '@'; /* $@ */ state = XARG; } - zero_ok = true; /* POSIX 2009? */ + /* POSIX 2009? */ + zero_ok = true; } else { if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') && p[2] == ']') { XPtrV wv; - switch (stype & 0x7f) { + switch (stype & 0x17F) { case '=': /* can't assign to a vector */ case '%': /* can't trim a vector (yet) */ case '#': case '?': case '0': case '/': + case 0x100 | '#': return (-1); } XPinit(wv, 32); @@ -1073,13 +1177,13 @@ varsub(Expand *xp, const char *sp, const char *word, } } else { /* Can't assign things like $! or $1 */ - if ((stype & 0x7f) == '=' && + if ((stype & 0x17F) == '=' && ctype(*sp, C_VAR1 | C_DIGIT)) return (-1); if (*sp == '!' && sp[1]) { ++sp; xp->var = global(sp); - if (cstrchr(sp, '[')) { + if (vstrchr(sp, '[')) { if (xp->var->flag & ISSET) xp->str = shf_smprintf("%lu", arrayindex(xp->var)); @@ -1088,7 +1192,8 @@ varsub(Expand *xp, const char *sp, const char *word, } else if (xp->var->flag & ISSET) xp->str = xp->var->name; else - xp->str = "0"; /* ksh93 compat */ + /* ksh93 compat */ + xp->str = "0"; } else { xp->var = global(sp); xp->str = str_val(xp->var); @@ -1097,15 +1202,17 @@ varsub(Expand *xp, const char *sp, const char *word, } } - c = stype&0x7f; + c = stype & 0x7F; /* test the compiler's code generator */ - if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' || + if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' || (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */ - c == '=' || c == '-' || c == '?' : c == '+')) - state = XBASE; /* expand word instead of variable value */ + c == '=' || c == '-' || c == '?' : c == '+'))) || + stype == (0x80 | '0') || stype == (0x100 | '#')) + /* expand word instead of variable value */ + state = XBASE; if (Flag(FNOUNSET) && xp->str == null && !zero_ok && (ctype(c, C_SUBOP2) || (state != XBASE && c != '+'))) - errorf("%s: parameter not set", sp); + errorf("%s: %s", sp, "parameter not set"); return (state); } @@ -1118,30 +1225,33 @@ comsub(Expand *xp, const char *cp) Source *s, *sold; struct op *t; struct shf *shf; + uint8_t old_utfmode = UTFMODE; s = pushs(SSTRING, ATEMP); s->start = s->str = cp; sold = source; - t = compile(s); + t = compile(s, true); afree(s, ATEMP); source = sold; if (t == NULL) return (XBASE); - if (t != NULL && t->type == TCOM && /* $(<file) */ + if (t != NULL && t->type == TCOM && *t->args == NULL && *t->vars == NULL && t->ioact != NULL) { + /* $(<file) */ struct ioword *io = *t->ioact; char *name; - if ((io->flag&IOTYPE) != IOREAD) - errorf("funny $() command: %s", + if ((io->flag & IOTYPE) != IOREAD) + errorf("%s: %s", "funny $() command", snptreef(NULL, 32, "%R", io)); shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC); if (shf == NULL) - errorf("%s: cannot open $() input", name); - xp->split = 0; /* no waitlast() */ + errorf("%s: %s %s", name, "can't open", "$() input"); + /* no waitlast() */ + xp->split = 0; } else { int ofd1, pv[2]; openpipe(pv); @@ -1154,9 +1264,11 @@ comsub(Expand *xp, const char *cp) execute(t, XFORK|XXCOM|XPIPEO, NULL); restfd(1, ofd1); startlast(); - xp->split = 1; /* waitlast() */ + /* waitlast() */ + xp->split = 1; } + UTFMODE = old_utfmode; xp->u.shf = shf; return (XCOM); } @@ -1172,7 +1284,8 @@ trimsub(char *str, char *pat, int how) char *p, c; switch (how & 0xFF) { - case '#': /* shortest at beginning */ + case '#': + /* shortest match at beginning */ for (p = str; p <= end; p += utf_ptradj(p)) { c = *p; *p = '\0'; if (gmatchx(str, pat, false)) { @@ -1182,7 +1295,8 @@ trimsub(char *str, char *pat, int how) *p = c; } break; - case '#'|0x80: /* longest match at beginning */ + case '#'|0x80: + /* longest match at beginning */ for (p = end; p >= str; p--) { c = *p; *p = '\0'; if (gmatchx(str, pat, false)) { @@ -1192,7 +1306,8 @@ trimsub(char *str, char *pat, int how) *p = c; } break; - case '%': /* shortest match at end */ + case '%': + /* shortest match at end */ p = end; while (p >= str) { if (gmatchx(p, pat, false)) @@ -1207,7 +1322,8 @@ trimsub(char *str, char *pat, int how) --p; } break; - case '%'|0x80: /* longest match at end */ + case '%'|0x80: + /* longest match at end */ for (p = str; p <= end; p++) if (gmatchx(p, pat, false)) { trimsub_match: @@ -1217,7 +1333,8 @@ trimsub(char *str, char *pat, int how) break; } - return (str); /* no match, return string */ + /* no match, return string */ + return (str); } /* @@ -1243,7 +1360,8 @@ glob(char *cp, XPtrV *wp, int markdirs) #define GF_GLOBBED BIT(1) /* some globbing has been done */ #define GF_MARKDIR BIT(2) /* add trailing / to directories */ -/* Apply file globbing to cp and store the matching files in wp. Returns +/* + * Apply file globbing to cp and store the matching files in wp. Returns * the number of matches found. */ int @@ -1275,8 +1393,10 @@ globit(XString *xs, /* dest string */ /* This to allow long expansions to be interrupted */ intrcheck(); - if (sp == NULL) { /* end of source path */ - /* We only need to check if the file exists if a pattern + if (sp == NULL) { + /* end of source path */ + /* + * We only need to check if the file exists if a pattern * is followed by a non-pattern (eg, foo*x/bar; no check * is needed for foo* since the match must exist) or if * any patterns were expanded and the markdirs option is set. @@ -1292,7 +1412,8 @@ globit(XString *xs, /* dest string */ if (lstat(Xstring(*xs, xp), &lstatb) < 0) return; - /* special case for systems which strip trailing + /* + * special case for systems which strip trailing * slashes from regular files (eg, /etc/passwd/). * SunOS 4.1.3 does this... */ @@ -1301,7 +1422,8 @@ globit(XString *xs, /* dest string */ (!S_ISLNK(lstatb.st_mode) || stat_check() < 0 || !S_ISDIR(statb.st_mode))) return; - /* Possibly tack on a trailing / if there isn't already + /* + * Possibly tack on a trailing / if there isn't already * one and if the file is a directory or a symlink to a * directory */ @@ -1328,7 +1450,8 @@ globit(XString *xs, /* dest string */ np = strchr(sp, '/'); if (np != NULL) { se = np; - odirsep = *np; /* don't assume '/', can be multiple kinds */ + /* don't assume '/', can be multiple kinds */ + odirsep = *np; *np++ = '\0'; } else { odirsep = '\0'; /* keep gcc quiet */ @@ -1336,7 +1459,8 @@ globit(XString *xs, /* dest string */ } - /* Check if sp needs globbing - done to avoid pattern checks for strings + /* + * Check if sp needs globbing - done to avoid pattern checks for strings * containing MAGIC characters, open [s without the matching close ], * etc. (otherwise opendir() will be called which may fail because the * directory isn't readable - if no globbing is needed, only execute @@ -1352,8 +1476,7 @@ globit(XString *xs, /* dest string */ DIR *dirp; struct dirent *d; char *name; - int len; - int prefix_len; + size_t len, prefix_len; /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */ *xp = '\0'; @@ -1365,7 +1488,8 @@ globit(XString *xs, /* dest string */ name = d->d_name; if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) - continue; /* always ignore . and .. */ + /* always ignore . and .. */ + continue; if ((*name == '.' && *sp != '.') || !gmatchx(name, sp, true)) continue; @@ -1416,7 +1540,8 @@ debunk(char *dp, const char *sp, size_t dlen) return (dp); } -/* Check if p is an unquoted name, possibly followed by a / or :. If so +/* + * Check if p is an unquoted name, possibly followed by a / or :. If so * puts the expanded version in *dcp,dp and returns a pointer in p just * past the name, otherwise returns 0. */ @@ -1534,7 +1659,8 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) } /* no valid expansions... */ if (!p || count != 0) { - /* Note that given a{{b,c} we do not expand anything (this is + /* + * Note that given a{{b,c} we do not expand anything (this is * what AT&T ksh does. This may be changed to do the {b,c} * expansion. } */ @@ -1562,6 +1688,10 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) char *news; int l1, l2, l3; + /* + * addition safe since these operate on + * one string (separate substrings) + */ l1 = brace_start - start; l2 = (p - 1) - field_start; l3 = end - brace_end; @@ -1,7 +1,7 @@ /* $OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,39 +22,42 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.75 2010/07/17 22:09:34 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.96 2011/09/07 15:24:14 tg Exp $"); #ifndef MKSH_DEFAULT_EXECSHELL #define MKSH_DEFAULT_EXECSHELL "/bin/sh" #endif -static int comexec(struct op *, struct tbl *volatile, const char **, +static int comexec(struct op *, struct tbl * volatile, const char **, int volatile, volatile int *); static void scriptexec(struct op *, const char **) MKSH_A_NORETURN; static int call_builtin(struct tbl *, const char **); static int iosetup(struct ioword *, struct tbl *); -static int herein(const char *, int); +static int herein(const char *, int, char **); static const char *do_selectargs(const char **, bool); static Test_op dbteste_isa(Test_env *, Test_meta); static const char *dbteste_getopnd(Test_env *, Test_op, bool); static void dbteste_error(Test_env *, int, const char *); +static int search_access(const char *, int); /* * execute command tree */ int -execute(struct op *volatile t, - volatile int flags, /* if XEXEC don't fork */ +execute(struct op * volatile t, + /* if XEXEC don't fork */ + volatile int flags, volatile int * volatile xerrok) { int i; volatile int rv = 0, dummy = 0; int pv[2]; - const char ** volatile ap; + const char ** volatile ap = NULL; char ** volatile up; - const char *s, *cp; + const char *s, *ccp; struct ioword **iowp; struct tbl *tp = NULL; + char *cp; if (t == NULL) return (0); @@ -71,15 +74,60 @@ execute(struct op *volatile t, if (trap) runtraps(0); + /* we want to run an executable, do some variance checks */ if (t->type == TCOM) { - /* Clear subst_exstat before argument expansion. Used by + /* check if this is 'var=<<EOF' */ + if ( + /* we have zero arguments, i.e. no programme to run */ + t->args[0] == NULL && + /* we have exactly one variable assignment */ + t->vars[0] != NULL && t->vars[1] == NULL && + /* we have exactly one I/O redirection */ + t->ioact != NULL && t->ioact[0] != NULL && + t->ioact[1] == NULL && + /* of type "here document" (or "here string") */ + (t->ioact[0]->flag & IOTYPE) == IOHERE && + /* the variable assignment begins with a valid varname */ + (ccp = skip_wdvarname(t->vars[0], true)) != t->vars[0] && + /* and has no right-hand side (i.e. "varname=") */ + ccp[0] == CHAR && ccp[1] == '=' && ccp[2] == EOS && + /* plus we can have a here document content */ + herein(t->ioact[0]->heredoc, t->ioact[0]->flag & IOEVAL, + &cp) == 0 && cp && *cp) { + char *sp = cp, *dp; + size_t n = ccp - t->vars[0] + 2, z; + + /* drop redirection (will be garbage collected) */ + t->ioact = NULL; + + /* set variable to its expanded value */ + z = strlen(cp) + 1; + if (notoktomul(z, 2) || notoktoadd(z * 2, n)) + internal_errorf(Toomem, (unsigned long)-1); + dp = alloc(z * 2 + n, ATEMP); + memcpy(dp, t->vars[0], n); + t->vars[0] = dp; + dp += n; + while (*sp) { + *dp++ = QCHAR; + *dp++ = *sp++; + } + *dp = EOS; + /* free the expanded value */ + afree(cp, APERM); + } + + /* + * Clear subst_exstat before argument expansion. Used by * null commands (see comexec() and c_eval()) and by c_set(). */ subst_exstat = 0; - current_lineno = t->lineno; /* for $LINENO */ + /* for $LINENO */ + current_lineno = t->lineno; - /* POSIX says expand command words first, then redirections, + /* + * POSIX says expand command words first, then redirections, * and assignments last.. */ up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE); @@ -88,8 +136,8 @@ execute(struct op *volatile t, timex_hook(t, &up); ap = (const char **)up; if (Flag(FXTRACE) && ap[0]) { - shf_fprintf(shl_out, "%s", - substitute(str_val(global("PS4")), 0)); + shf_puts(substitute(str_val(global("PS4")), 0), + shl_out); for (i = 0; ap[i]; i++) shf_fprintf(shl_out, "%s%c", ap[i], ap[i + 1] ? ' ' : '\n'); @@ -101,17 +149,21 @@ execute(struct op *volatile t, flags &= ~XTIME; if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) { - e->savefd = alloc(NUFILE * sizeof(short), ATEMP); + e->savefd = alloc2(NUFILE, sizeof(short), ATEMP); /* initialise to not redirected */ memset(e->savefd, 0, NUFILE * sizeof(short)); } + /* mark for replacement later (unless TPIPE) */ + vp_pipest->flag |= INT_L; + /* do redirection, to be restored in quitenv() */ if (t->ioact != NULL) for (iowp = t->ioact; *iowp != NULL; iowp++) { if (iosetup(*iowp, tp) < 0) { exstat = rv = 1; - /* Redirection failures for special commands + /* + * Redirection failures for special commands * cause (non-interactive) shell to exit. */ if (tp && tp->type == CSHELL && @@ -138,7 +190,8 @@ execute(struct op *volatile t, e->savefd[1] = savefd(1); while (t->type == TPIPE) { openpipe(pv); - ksh_dup2(pv[1], 1, false); /* stdout of curr */ + /* stdout of curr */ + ksh_dup2(pv[1], 1, false); /** * Let exchild() close pv[0] in child * (if this isn't done, commands like @@ -147,15 +200,18 @@ execute(struct op *volatile t, */ exchild(t->left, flags | XPIPEO | XCCLOSE, NULL, pv[0]); - ksh_dup2(pv[0], 0, false); /* stdin of next */ + /* stdin of next */ + ksh_dup2(pv[0], 0, false); closepipe(pv); flags |= XPIPEI; t = t->right; } - restfd(1, e->savefd[1]); /* stdout of last */ - e->savefd[1] = 0; /* no need to re-restore this */ + /* stdout of last */ + restfd(1, e->savefd[1]); + /* no need to re-restore this */ + e->savefd[1] = 0; /* Let exchild() close 0 in parent, after fork, before wait */ - i = exchild(t, flags | XPCLOSE, xerrok, 0); + i = exchild(t, flags | XPCLOSE | XPIPEST, xerrok, 0); if (!(flags&XBGND) && !(flags&XXCOM)) rv = i; break; @@ -169,9 +225,11 @@ execute(struct op *volatile t, break; case TCOPROC: { +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; - /* Block sigchild as we are using things changed in the + /* + * Block sigchild as we are using things changed in the * signal handler */ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); @@ -183,6 +241,7 @@ execute(struct op *volatile t, unwind(i); /* NOTREACHED */ } +#endif /* Already have a (live) co-process? */ if (coproc.job && coproc.write >= 0) errorf("coprocess already exists"); @@ -208,15 +267,20 @@ execute(struct op *volatile t, openpipe(pv); coproc.read = pv[0]; ksh_dup2(pv[1], 1, false); - coproc.readw = pv[1]; /* closed before first read */ + /* closed before first read */ + coproc.readw = pv[1]; coproc.njobs = 0; /* create new coprocess id */ ++coproc.id; } +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); - e->type = E_EXEC; /* no more need for error handler */ + /* no more need for error handler */ + e->type = E_EXEC; +#endif - /* exchild() closes coproc.* in child after fork, + /* + * exchild() closes coproc.* in child after fork, * will also increment coproc.njobs when the * job is actually created. */ @@ -227,7 +291,8 @@ execute(struct op *volatile t, } case TASYNC: - /* XXX non-optimal, I think - "(foo &)", forks for (), + /* + * XXX non-optimal, I think - "(foo &)", forks for (), * forks again for async... parent should optimise * this to "foo &"... */ @@ -272,7 +337,7 @@ execute(struct op *volatile t, (const char **)eval((const char **)t->vars, DOBLANK | DOGLOB | DOTILDE); e->type = E_LOOP; - while (1) { + while (/* CONSTCOND */ 1) { i = sigsetjmp(e->jbuf, 0); if (!i) break; @@ -285,20 +350,22 @@ execute(struct op *volatile t, goto Break; } } - rv = 0; /* in case of a continue */ + /* in case of a continue */ + rv = 0; if (t->type == TFOR) { while (*ap != NULL) { setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); rv = execute(t->left, flags & XERROK, xerrok); } - } else { /* TSELECT */ + } else { + /* TSELECT */ for (;;) { - if (!(cp = do_selectargs(ap, is_first))) { + if (!(ccp = do_selectargs(ap, is_first))) { rv = 1; break; } is_first = false; - setstr(global(t->str), cp, KSH_UNWIND_ERROR); + setstr(global(t->str), ccp, KSH_UNWIND_ERROR); execute(t->left, flags & XERROK, xerrok); } } @@ -308,7 +375,7 @@ execute(struct op *volatile t, case TWHILE: case TUNTIL: e->type = E_LOOP; - while (1) { + while (/* CONSTCOND */ 1) { i = sigsetjmp(e->jbuf, 0); if (!i) break; @@ -321,7 +388,8 @@ execute(struct op *volatile t, goto Break; } } - rv = 0; /* in case of a continue */ + /* in case of a continue */ + rv = 0; while ((execute(t->left, XERROK, NULL) == 0) == (t->type == TWHILE)) rv = execute(t->right, flags & XERROK, xerrok); @@ -330,22 +398,38 @@ execute(struct op *volatile t, case TIF: case TELIF: if (t->right == NULL) - break; /* should be error */ + /* should be error */ + break; rv = execute(t->left, XERROK, NULL) == 0 ? execute(t->right->left, flags & XERROK, xerrok) : execute(t->right->right, flags & XERROK, xerrok); break; case TCASE: - cp = evalstr(t->str, DOTILDE); - for (t = t->left; t != NULL && t->type == TPAT; t = t->right) - for (ap = (const char **)t->vars; *ap; ap++) - if ((s = evalstr(*ap, DOTILDE|DOPAT)) && - gmatchx(cp, s, false)) - goto Found; - break; - Found: - rv = execute(t->left, flags & XERROK, xerrok); + i = 0; + ccp = evalstr(t->str, DOTILDE); + for (t = t->left; t != NULL && t->type == TPAT; t = t->right) { + for (ap = (const char **)t->vars; *ap; ap++) { + if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) && + gmatchx(ccp, s, false))) { + rv = execute(t->left, flags & XERROK, + xerrok); + i = 0; + switch (t->u.charflag) { + case '&': + i = 1; + /* FALLTHROUGH */ + case '|': + goto TCASE_next; + } + goto TCASE_out; + } + } + i = 0; + TCASE_next: + /* empty */; + } + TCASE_out: break; case TBRACE: @@ -357,13 +441,15 @@ execute(struct op *volatile t, break; case TTIME: - /* Clear XEXEC so nested execute() call doesn't exit + /* + * Clear XEXEC so nested execute() call doesn't exit * (allows "ls -l | time grep foo"). */ rv = timex(t, flags & ~XEXEC, xerrok); break; - case TEXEC: /* an eval'd TCOM */ + case TEXEC: + /* an eval'd TCOM */ s = t->args[0]; up = makenv(); restoresigs(); @@ -382,13 +468,21 @@ execute(struct op *volatile t, } Break: exstat = rv; + if (vp_pipest->flag & INT_L) { + unset(vp_pipest, 1); + vp_pipest->flag = DEFINED | ISSET | INTEGER | RDONLY | + ARRAY | INT_U; + vp_pipest->val.i = rv; + } - quitenv(NULL); /* restores IO */ + /* restores IO */ + quitenv(NULL); if ((flags&XEXEC)) - unwind(LEXIT); /* exit child */ + /* exit child */ + unwind(LEXIT); if (rv != 0 && !(flags & XERROK) && (xerrok == NULL || !*xerrok)) { - trapsig(SIGERR_); + trapsig(ksh_SIGERR); if (Flag(FERREXIT)) unwind(LERROR); } @@ -400,21 +494,23 @@ execute(struct op *volatile t, */ static int -comexec(struct op *t, struct tbl *volatile tp, const char **ap, +comexec(struct op *t, struct tbl * volatile tp, const char **ap, volatile int flags, volatile int *xerrok) { int i; volatile int rv = 0; const char *cp; const char **lastp; - static struct op texec; /* Must be static (XXX but why?) */ + /* Must be static (XXX but why?) */ + static struct op texec; int type_flags; int keepasn_ok; int fcflags = FC_BI|FC_FUNC|FC_PATH; bool bourne_function_call = false; struct block *l_expand, *l_assign; - /* snag the last argument for $_ XXX not the same as AT&T ksh, + /* + * snag the last argument for $_ XXX not the same as AT&T ksh, * which only seems to set $_ after a newline (but not in * functions/dot scripts, but in interactive and script) - * perhaps save last arg here and set it in shell()?. @@ -427,7 +523,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, KSH_RETURN_ERROR); } - /* Deal with the shell builtins builtin, exec and command since + /** + * Deal with the shell builtins builtin, exec and command since * they can be followed by other commands. This must be done before * we know if we should create a local block which must be done * before we can do a path search (in case the assignments change @@ -441,15 +538,16 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, */ keepasn_ok = 1; while (tp && tp->type == CSHELL) { - fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */ + /* undo effects of command */ + fcflags = FC_BI|FC_FUNC|FC_PATH; if (tp->val.f == c_builtin) { - if ((cp = *++ap) == NULL) { + if ((cp = *++ap) == NULL || + (!strcmp(cp, "--") && (cp = *++ap) == NULL)) { tp = NULL; break; } - tp = findcom(cp, FC_BI); - if (tp == NULL) - errorf("builtin: %s: not a builtin", cp); + if ((tp = findcom(cp, FC_BI)) == NULL) + errorf("%s: %s: %s", Tbuiltin, cp, "not a builtin"); continue; } else if (tp->val.f == c_exec) { if (ap[1] == NULL) @@ -459,27 +557,30 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, } else if (tp->val.f == c_command) { int optc, saw_p = 0; - /* Ugly dealing with options in two places (here and - * in c_command(), but such is life) + /* + * Ugly dealing with options in two places (here + * and in c_command(), but such is life) */ ksh_getopt_reset(&builtin_opt, 0); while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p') saw_p = 1; if (optc != EOF) - break; /* command -vV or something */ + /* command -vV or something */ + break; /* don't look for functions */ fcflags = FC_BI|FC_PATH; if (saw_p) { if (Flag(FRESTRICTED)) { - warningf(true, - "command -p: restricted"); + warningf(true, "%s: %s", + "command -p", "restricted"); rv = 1; goto Leave; } fcflags |= FC_DEFPATH; } ap += builtin_opt.optind; - /* POSIX says special builtins lose their status + /* + * POSIX says special builtins lose their status * if accessed using command. */ keepasn_ok = 0; @@ -488,6 +589,25 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, subst_exstat = 0; break; } +#ifndef MKSH_NO_EXTERNAL_CAT + } else if (tp->val.f == c_cat) { + /* + * if we have any flags, do not use the builtin + * in theory, we could allow -u, but that would + * mean to use ksh_getopt here and possibly ad- + * ded complexity and more code and isn't worth + * additional hassle (and the builtin must call + * ksh_getopt already but can't come back here) + */ + if (ap[1] && ap[1][0] == '-' && ap[1][1] != '\0' && + /* argument, begins with -, is not - or -- */ + (ap[1][1] != '-' || ap[1][2] != '\0')) + /* don't look for builtins or functions */ + fcflags = FC_PATH; + else + /* go on, use the builtin */ + break; +#endif } else break; tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC)); @@ -518,8 +638,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, if (Flag(FXTRACE)) { if (i == 0) - shf_fprintf(shl_out, "%s", - substitute(str_val(global("PS4")), 0)); + shf_puts(substitute(str_val(global("PS4")), 0), + shl_out); shf_fprintf(shl_out, "%s%c", cp, t->vars[i + 1] ? ' ' : '\n'); if (!t->vars[i + 1]) @@ -535,7 +655,7 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, goto Leave; } else if (!tp) { if (Flag(FRESTRICTED) && vstrchr(cp, '/')) { - warningf(true, "%s: restricted", cp); + warningf(true, "%s: %s", cp, "restricted"); rv = 1; goto Leave; } @@ -543,54 +663,49 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, } switch (tp->type) { - case CSHELL: /* shell built-in */ + + /* shell built-in */ + case CSHELL: rv = call_builtin(tp, (const char **)ap); break; - case CFUNC: { /* function call */ + /* function call */ + case CFUNC: { volatile unsigned char old_xflag; - volatile Tflag old_inuse; - const char *volatile old_kshname; + volatile uint32_t old_inuse; + const char * volatile old_kshname; if (!(tp->flag & ISSET)) { struct tbl *ftp; if (!tp->u.fpath) { - if (tp->u2.errno_) { - warningf(true, - "%s: can't find function " - "definition file - %s", - cp, strerror(tp->u2.errno_)); - rv = 126; - } else { - warningf(true, - "%s: can't find function " - "definition file", cp); - rv = 127; - } + rv = (tp->u2.errnov == ENOENT) ? 127 : 126; + warningf(true, "%s: %s %s: %s", cp, + "can't find", "function definition file", + strerror(tp->u2.errnov)); break; } if (include(tp->u.fpath, 0, NULL, 0) < 0) { rv = errno; - warningf(true, - "%s: can't open function definition file %s - %s", - cp, tp->u.fpath, strerror(rv)); + warningf(true, "%s: %s %s %s: %s", cp, + "can't open", "function definition file", + tp->u.fpath, strerror(rv)); rv = 127; break; } if (!(ftp = findfunc(cp, hash(cp), false)) || !(ftp->flag & ISSET)) { - warningf(true, - "%s: function not defined by %s", - cp, tp->u.fpath); + warningf(true, "%s: %s %s", cp, + "function not defined by", tp->u.fpath); rv = 127; break; } tp = ftp; } - /* ksh functions set $0 to function name, POSIX functions leave - * $0 unchanged. + /* + * ksh functions set $0 to function name, POSIX + * functions leave $0 unchanged. */ old_kshname = kshname; if (tp->flag & FKSH) @@ -601,7 +716,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, for (i = 0; *ap++ != NULL; i++) ; e->loc->argc = i - 1; - /* ksh-style functions handle getopts sanely, + /* + * ksh-style functions handle getopts sanely, * Bourne/POSIX functions are insane... */ if (tp->flag & FKSH) { @@ -611,7 +727,7 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, } old_xflag = Flag(FXTRACE); - Flag(FXTRACE) = tp->flag & TRACE ? 1 : 0; + Flag(FXTRACE) |= tp->flag & TRACE ? 1 : 0; old_inuse = tp->flag & FINUSE; tp->flag |= FINUSE; @@ -626,9 +742,10 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, kshname = old_kshname; Flag(FXTRACE) = old_xflag; tp->flag = (tp->flag & ~FINUSE) | old_inuse; - /* Were we deleted while executing? If so, free the execution - * tree. todo: Unfortunately, the table entry is never re-used - * until the lookup table is expanded. + /* + * Were we deleted while executing? If so, free the + * execution tree. TODO: Unfortunately, the table entry + * is never re-used until the lookup table is expanded. */ if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) { if (tp->flag & ALLOC) { @@ -651,26 +768,23 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, /* NOTREACHED */ default: quitenv(NULL); - internal_errorf("CFUNC %d", i); + internal_errorf("%s %d", "CFUNC", i); } break; } - case CEXEC: /* executable command */ - case CTALIAS: /* tracked alias */ + /* executable command */ + case CEXEC: + /* tracked alias */ + case CTALIAS: if (!(tp->flag&ISSET)) { - /* errno_ will be set if the named command was found - * but could not be executed (permissions, no execute - * bit, directory, etc). Print out a (hopefully) - * useful error message and set the exit status to 126. - */ - if (tp->u2.errno_) { - warningf(true, "%s: cannot execute - %s", cp, - strerror(tp->u2.errno_)); - rv = 126; /* POSIX */ - } else { - warningf(true, "%s: not found", cp); + if (tp->u2.errnov == ENOENT) { rv = 127; + warningf(true, "%s: %s", cp, "not found"); + } else { + rv = 126; + warningf(true, "%s: %s: %s", cp, "can't execute", + strerror(tp->u2.errnov)); } break; } @@ -694,7 +808,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap, /* to fork we set up a TEXEC node and call execute */ texec.type = TEXEC; - texec.left = t; /* for tprint */ + /* for tprint */ + texec.left = t; texec.str = tp->val.s; texec.args = ap; rv = exchild(&texec, flags, xerrok, -1); @@ -714,14 +829,15 @@ scriptexec(struct op *tp, const char **ap) const char *sh; #ifndef MKSH_SMALL unsigned char *cp; - char buf[64]; /* 64 == MAXINTERP in MirBSD <sys/param.h> */ + /* 64 == MAXINTERP in MirBSD <sys/param.h> */ + char buf[64]; int fd; #endif union mksh_ccphack args, cap; sh = str_val(global("EXECSHELL")); if (sh && *sh) - sh = search(sh, path, X_OK, NULL); + sh = search_path(sh, path, X_OK, NULL); if (!sh || !*sh) sh = MKSH_DEFAULT_EXECSHELL; @@ -734,8 +850,15 @@ scriptexec(struct op *tp, const char **ap) /* read error -> no good */ buf[0] = '\0'; close(fd); - /* scan for newline (or CR) or NUL _before_ end of buffer */ + + /* skip UTF-8 Byte Order Mark, if present */ cp = (unsigned char *)buf; + if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF)) + cp += 3; + /* save begin of shebang for later */ + fd = (char *)cp - buf; /* either 0 or (if BOM) 3 */ + + /* scan for newline (or CR) or NUL _before_ end of buffer */ while ((char *)cp < (buf + sizeof(buf))) if (*cp == '\0' || *cp == '\n' || *cp == '\r') { *cp = '\0'; @@ -745,13 +868,13 @@ scriptexec(struct op *tp, const char **ap) /* if the shebang line is longer than MAXINTERP, bail out */ if ((char *)cp >= (buf + sizeof(buf))) goto noshebang; - /* skip UTF-8 Byte Order Mark, if present */ - cp = (unsigned char *)buf; - if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF)) - cp += 3; + + /* restore begin of shebang position (buf+0 or buf+3) */ + cp = (unsigned char *)(buf + fd); /* bail out if read error (above) or no shebang */ if ((cp[0] != '#') || (cp[1] != '!')) goto noshebang; + cp += 2; /* skip whitespace before shell name */ while (*cp == ' ' || *cp == '\t') @@ -806,7 +929,7 @@ shcomexec(const char **wp) tp = ktsearch(&builtins, *wp, hash(*wp)); if (tp == NULL) - internal_errorf("shcomexec: %s", *wp); + internal_errorf("%s: %s", "shcomexec", *wp); return (call_builtin(tp, wp)); } @@ -842,20 +965,31 @@ findfunc(const char *name, uint32_t h, bool create) int define(const char *name, struct op *t) { + uint32_t nhash; struct tbl *tp; bool was_set = false; - while (1) { - tp = findfunc(name, hash(name), true); + nhash = hash(name); + + if (t != NULL && !tobool(t->u.ksh_func)) { + /* drop same-name aliases for POSIX functions */ + if ((tp = ktsearch(&aliases, name, nhash))) + ktdelete(tp); + } + + while (/* CONSTCOND */ 1) { + tp = findfunc(name, nhash, true); if (tp->flag & ISSET) was_set = true; - /* If this function is currently being executed, we zap this - * table entry so findfunc() won't see it + /* + * If this function is currently being executed, we zap + * this table entry so findfunc() won't see it */ if (tp->flag & FINUSE) { tp->name[0] = '\0'; - tp->flag &= ~DEFINED; /* ensure it won't be found */ + /* ensure it won't be found */ + tp->flag &= ~DEFINED; tp->flag |= FDELETE; } else break; @@ -866,7 +1000,8 @@ define(const char *name, struct op *t) tfree(tp->val.t, tp->areap); } - if (t == NULL) { /* undefine */ + if (t == NULL) { + /* undefine */ ktdelete(tp); return (was_set ? 0 : 1); } @@ -882,19 +1017,22 @@ define(const char *name, struct op *t) /* * add builtin */ -void +const char * builtin(const char *name, int (*func) (const char **)) { struct tbl *tp; - Tflag flag; + uint32_t flag; /* see if any flags should be set for this builtin */ for (flag = 0; ; name++) { - if (*name == '=') /* command does variable assignment */ + if (*name == '=') + /* command does variable assignment */ flag |= KEEPASN; - else if (*name == '*') /* POSIX special builtin */ + else if (*name == '*') + /* POSIX special builtin */ flag |= SPEC_BI; - else if (*name == '+') /* POSIX regular builtin */ + else if (*name == '+') + /* POSIX regular builtin */ flag |= REG_BI; else break; @@ -904,6 +1042,8 @@ builtin(const char *name, int (*func) (const char **)) tp->flag = DEFINED | flag; tp->type = CSHELL; tp->val.f = func; + + return (name); } /* @@ -916,8 +1056,10 @@ findcom(const char *name, int flags) static struct tbl temp; uint32_t h = hash(name); struct tbl *tp = NULL, *tbi; - unsigned char insert = Flag(FTRACKALL); /* insert if not found */ - char *fpath; /* for function autoloading */ + /* insert if not found */ + unsigned char insert = Flag(FTRACKALL); + /* for function autoloading */ + char *fpath; union mksh_cchack npath; if (vstrchr(name, '/')) { @@ -927,7 +1069,8 @@ findcom(const char *name, int flags) goto Search; } tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL; - /* POSIX says special builtins first, then functions, then + /* + * POSIX says special builtins first, then functions, then * POSIX regular builtins, then search path... */ if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI)) @@ -937,10 +1080,10 @@ findcom(const char *name, int flags) if (tp && !(tp->flag & ISSET)) { if ((fpath = str_val(global("FPATH"))) == null) { tp->u.fpath = NULL; - tp->u2.errno_ = 0; + tp->u2.errnov = ENOENT; } else - tp->u.fpath = search(name, fpath, R_OK, - &tp->u2.errno_); + tp->u.fpath = search_path(name, fpath, R_OK, + &tp->u2.errnov); } } if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI)) @@ -949,7 +1092,8 @@ findcom(const char *name, int flags) tp = tbi; if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) { tp = ktsearch(&taliases, name, h); - if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) { + if (tp && (tp->flag & ISSET) && + ksh_access(tp->val.s, X_OK) != 0) { if (tp->flag & ALLOC) { tp->flag &= ~ALLOC; afree(tp->val.s, APERM); @@ -969,10 +1113,12 @@ findcom(const char *name, int flags) tp = &temp; tp->type = CEXEC; } - tp->flag = DEFINED; /* make ~ISSET */ + /* make ~ISSET */ + tp->flag = DEFINED; } - npath.ro = search(name, flags & FC_DEFPATH ? def_path : path, - X_OK, &tp->u2.errno_); + npath.ro = search_path(name, + (flags & FC_DEFPATH) ? def_path : path, + X_OK, &tp->u2.errnov); if (npath.ro) { strdupx(tp->val.s, npath.ro, APERM); if (npath.ro != name) @@ -980,16 +1126,18 @@ findcom(const char *name, int flags) tp->flag |= ISSET|ALLOC; } else if ((flags & FC_FUNC) && (fpath = str_val(global("FPATH"))) != null && - (npath.ro = search(name, fpath, R_OK, - &tp->u2.errno_)) != NULL) { - /* An undocumented feature of AT&T ksh is that it - * searches FPATH if a command is not found, even - * if the command hasn't been set up as an autoloaded - * function (ie, no typeset -uf). + (npath.ro = search_path(name, fpath, R_OK, + &tp->u2.errnov)) != NULL) { + /* + * An undocumented feature of AT&T ksh is that + * it searches FPATH if a command is not found, + * even if the command hasn't been set up as an + * autoloaded function (ie, no typeset -uf). */ tp = &temp; tp->type = CFUNC; - tp->flag = DEFINED; /* make ~ISSET */ + /* make ~ISSET */ + tp->flag = DEFINED; tp->u.fpath = npath.ro; } } @@ -998,9 +1146,10 @@ findcom(const char *name, int flags) /* * flush executable commands with relative paths + * (just relative or all?) */ void -flushcom(int all) /* just relative or all */ +flushcom(bool all) { struct tbl *tp; struct tstate ts; @@ -1015,49 +1164,50 @@ flushcom(int all) /* just relative or all */ } } -/* Check if path is something we want to find. Returns -1 for failure. */ -int -search_access(const char *lpath, int mode, - int *errnop) /* set if candidate found, but not suitable */ +/* check if path is something we want to find */ +static int +search_access(const char *fn, int mode) { - int ret, err = 0; - struct stat statb; - - if (stat(lpath, &statb) < 0) - return (-1); - ret = access(lpath, mode); - if (ret < 0) - err = errno; /* File exists, but we can't access it */ - else if (mode == X_OK && (!S_ISREG(statb.st_mode) || - !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { - /* This 'cause access() says root can execute everything */ - ret = -1; - err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; - } - if (err && errnop && !*errnop) - *errnop = err; - return (ret); + struct stat sb; + + if (stat(fn, &sb) < 0) + /* file does not exist */ + return (ENOENT); + /* LINTED use of access */ + if (access(fn, mode) < 0) + /* file exists, but we can't access it */ + return (errno); + if (mode == X_OK && (!S_ISREG(sb.st_mode) || + !(sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) + /* access(2) may say root can execute everything */ + return (S_ISDIR(sb.st_mode) ? EISDIR : EACCES); + return (0); } /* * search for command with PATH */ const char * -search(const char *name, const char *lpath, - int mode, /* R_OK or X_OK */ - int *errnop) /* set if candidate found, but not suitable */ +search_path(const char *name, const char *lpath, + /* R_OK or X_OK */ + int mode, + /* set if candidate found, but not suitable */ + int *errnop) { const char *sp, *p; char *xp; XString xs; - int namelen; + size_t namelen; + int ec = 0, ev; - if (errnop) - *errnop = 0; if (vstrchr(name, '/')) { - if (search_access(name, mode, errnop) == 0) + if ((ec = search_access(name, mode)) == 0) { + search_path_ok: + if (errnop) + *errnop = 0; return (name); - return (NULL); + } + goto search_path_err; } namelen = strlen(name) + 1; @@ -1077,12 +1227,20 @@ search(const char *name, const char *lpath, sp = p; XcheckN(xs, xp, namelen); memcpy(xp, name, namelen); - if (search_access(Xstring(xs, xp), mode, errnop) == 0) - return (Xclose(xs, xp + namelen)); + if ((ev = search_access(Xstring(xs, xp), mode)) == 0) { + name = Xclose(xs, xp + namelen); + goto search_path_ok; + } + /* accumulate non-ENOENT errors only */ + if (ev != ENOENT && ec == 0) + ec = ev; if (*sp++ == '\0') sp = NULL; } Xfree(xs, xp); + search_path_err: + if (errnop) + *errnop = ec ? ec : ENOENT; return (NULL); } @@ -1094,11 +1252,11 @@ call_builtin(struct tbl *tp, const char **wp) builtin_argv0 = wp[0]; builtin_flag = tp->flag; shf_reopen(1, SHF_WR, shl_stdout); - shl_stdout_ok = 1; + shl_stdout_ok = true; ksh_getopt_reset(&builtin_opt, GF_ERROR); rv = (*tp->val.f)(wp); shf_flush(shl_stdout); - shl_stdout_ok = 0; + shl_stdout_ok = false; builtin_flag = 0; builtin_argv0 = NULL; return (rv); @@ -1141,7 +1299,8 @@ iosetup(struct ioword *iop, struct tbl *tp) case IOWRITE: flags = O_WRONLY | O_CREAT | O_TRUNC; - /* The stat() is here to allow redirections to + /* + * The stat() is here to allow redirections to * things like /dev/null without error. */ if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) && @@ -1156,7 +1315,7 @@ iosetup(struct ioword *iop, struct tbl *tp) case IOHERE: do_open = 0; /* herein() returns -2 if error has been printed */ - u = herein(iop->heredoc, iop->flag & IOEVAL); + u = herein(iop->heredoc, iop->flag & IOEVAL, NULL); /* cp may have wrong name */ break; @@ -1165,7 +1324,8 @@ iosetup(struct ioword *iop, struct tbl *tp) do_open = 0; if (*cp == '-' && !cp[1]) { - u = 1009; /* prevent error return below */ + /* prevent error return below */ + u = 1009; do_close = 1; } else if ((u = check_fd(cp, X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK), @@ -1175,14 +1335,15 @@ iosetup(struct ioword *iop, struct tbl *tp) return (-1); } if (u == iop->unit) - return (0); /* "dup from" == "dup to" */ + /* "dup from" == "dup to" */ + return (0); break; } } if (do_open) { if (Flag(FRESTRICTED) && (flags & O_CREAT)) { - warningf(true, "%s: restricted", cp); + warningf(true, "%s: %s", cp, "restricted"); return (-1); } u = open(cp, flags, 0666); @@ -1191,7 +1352,7 @@ iosetup(struct ioword *iop, struct tbl *tp) /* herein() may already have printed message */ if (u == -1) { u = errno; - warningf(true, "cannot %s %s: %s", + warningf(true, "can't %s %s: %s", iotype == IODUP ? "dup" : (iotype == IOREAD || iotype == IOHERE) ? "open" : "create", cp, strerror(u)); @@ -1204,7 +1365,8 @@ iosetup(struct ioword *iop, struct tbl *tp) if (u == iop->unit) e->savefd[iop->unit] = -1; else - /* c_exec() assumes e->savefd[fd] set for any + /* + * c_exec() assumes e->savefd[fd] set for any * redirections. Ask savefd() not to close iop->unit; * this allows error messages to be seen if iop->unit * is 2; also means we can't lose the fd (eg, both @@ -1220,8 +1382,8 @@ iosetup(struct ioword *iop, struct tbl *tp) int ev; ev = errno; - warningf(true, - "could not finish (dup) redirection %s: %s", + warningf(true, "%s %s %s", + "can't finish (dup) redirection", snptreef(NULL, 32, "%R", &iotmp), strerror(ev)); if (iotype != IODUP) @@ -1230,84 +1392,111 @@ iosetup(struct ioword *iop, struct tbl *tp) } if (iotype != IODUP) close(u); - /* Touching any co-process fd in an empty exec + /* + * Touching any co-process fd in an empty exec * causes the shell to close its copies */ else if (tp && tp->type == CSHELL && tp->val.f == c_exec) { - if (iop->flag & IORDUP) /* possible exec <&p */ + if (iop->flag & IORDUP) + /* possible exec <&p */ coproc_read_close(u); - else /* possible exec >&p */ + else + /* possible exec >&p */ coproc_write_close(u); } } - if (u == 2) /* Clear any write errors */ + if (u == 2) + /* Clear any write errors */ shf_reopen(2, SHF_WR, shl_out); return (0); } /* - * open here document temp file. - * if unquoted here, expand here temp file into second temp file. + * Process here documents by providing the content, either as + * result (globally allocated) string or in a temp file; if + * unquoted, the string is expanded first. */ static int -herein(const char *content, int sub) +hereinval(const char *content, int sub, char **resbuf, struct shf *shf) +{ + const char *ccp; + struct source *s, *osource; + + osource = source; + newenv(E_ERRH); + if (sigsetjmp(e->jbuf, 0)) { + source = osource; + quitenv(shf); + /* special to iosetup(): don't print error */ + return (-2); + } + if (sub) { + /* do substitutions on the content of heredoc */ + s = pushs(SSTRING, ATEMP); + s->start = s->str = content; + source = s; + if (yylex(ONEWORD|HEREDOC) != LWORD) + internal_errorf("%s: %s", "herein", "yylex"); + source = osource; + ccp = evalstr(yylval.cp, 0); + } else + ccp = content; + + if (resbuf == NULL) + shf_puts(ccp, shf); + else + strdupx(*resbuf, ccp, APERM); + + quitenv(NULL); + return (0); +} + +static int +herein(const char *content, int sub, char **resbuf) { - volatile int fd = -1; - struct source *s, *volatile osource; - struct shf *volatile shf; + int fd = -1; + struct shf *shf; struct temp *h; int i; /* ksh -c 'cat << EOF' can cause this... */ if (content == NULL) { - warningf(true, "here document missing"); - return (-2); /* special to iosetup(): don't print error */ + warningf(true, "%s missing", "here document"); + /* special to iosetup(): don't print error */ + return (-2); } - /* Create temp file to hold content (done before newenv so temp - * doesn't get removed too soon). + /* skip all the fd setup if we just want the value */ + if (resbuf != NULL) + return (hereinval(content, sub, resbuf, NULL)); + + /* + * Create temp file to hold content (done before newenv + * so temp doesn't get removed too soon). */ h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps); if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) { - fd = errno; + i = errno; warningf(true, "can't %s temporary file %s: %s", - !shf ? "create" : "open", - h->name, strerror(fd)); + !shf ? "create" : "open", h->name, strerror(i)); if (shf) shf_close(shf); - return (-2 /* special to iosetup(): don't print error */); + /* special to iosetup(): don't print error */ + return (-2); } - osource = source; - newenv(E_ERRH); - i = sigsetjmp(e->jbuf, 0); - if (i) { - source = osource; - quitenv(shf); + if (hereinval(content, sub, NULL, shf) == -2) { close(fd); - return (-2); /* special to iosetup(): don't print error */ + /* special to iosetup(): don't print error */ + return (-2); } - if (sub) { - /* Do substitutions on the content of heredoc */ - s = pushs(SSTRING, ATEMP); - s->start = s->str = content; - source = s; - if (yylex(ONEWORD|HEREDOC) != LWORD) - internal_errorf("herein: yylex"); - source = osource; - shf_puts(evalstr(yylval.cp, 0), shf); - } else - shf_puts(content, shf); - - quitenv(NULL); if (shf_close(shf) == EOF) { i = errno; close(fd); - fd = errno; - warningf(true, "error writing %s: %s, %s", h->name, - strerror(i), strerror(fd)); - return (-2); /* special to iosetup(): don't print error */ + warningf(true, "%s: %s: %s", "write", h->name, strerror(i)); + /* special to iosetup(): don't print error */ + return (-2); } return (fd); @@ -1328,8 +1517,9 @@ do_selectargs(const char **ap, bool print_menu) for (argct = 0; ap[argct]; argct++) ; - while (1) { - /* Menu is printed if + while (/* CONSTCOND */ 1) { + /*- + * Menu is printed if * - this is the first time around the select loop * - the user enters a blank line * - the REPLY parameter is empty @@ -1353,11 +1543,11 @@ struct select_menu_info { int num_width; }; -static char *select_fmt_entry(char *, int, int, const void *); +static char *select_fmt_entry(char *, size_t, int, const void *); /* format a single select menu item */ static char * -select_fmt_entry(char *buf, int buflen, int i, const void *arg) +select_fmt_entry(char *buf, size_t buflen, int i, const void *arg) { const struct select_menu_info *smi = (const struct select_menu_info *)arg; @@ -1375,7 +1565,8 @@ pr_menu(const char * const *ap) { struct select_menu_info smi; const char * const *pp; - int acols = 0, aocts = 0, i, n; + size_t acols = 0, aocts = 0, i; + int n; /* * width/column calculations were done once and saved, but this @@ -1412,19 +1603,20 @@ pr_menu(const char * const *ap) } /* XXX: horrible kludge to fit within the framework */ -static char *plain_fmt_entry(char *, int, int, const void *); +static char *plain_fmt_entry(char *, size_t, int, const void *); static char * -plain_fmt_entry(char *buf, int buflen, int i, const void *arg) +plain_fmt_entry(char *buf, size_t buflen, int i, const void *arg) { - shf_snprintf(buf, buflen, "%s", ((const char * const *)arg)[i]); + strlcpy(buf, ((const char * const *)arg)[i], buflen); return (buf); } int pr_list(char * const *ap) { - int acols = 0, aocts = 0, i, n; + size_t acols = 0, aocts = 0, i; + int n; char * const *pp; for (n = 0, pp = ap; *pp; n++, pp++) { @@ -1468,8 +1660,10 @@ dbteste_isa(Test_env *te, Test_meta meta) if (meta == TM_UNOP || meta == TM_BINOP) { if (uqword) { - char buf[8]; /* longer than the longest operator */ + /* longer than the longest operator */ + char buf[8]; char *q = buf; + for (p = *te->pos.wp; *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2) *q++ = p[1]; @@ -1,7 +1,7 @@ /* $OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.49 2011/09/07 15:24:14 tg Exp $"); /* The order of these enums is constrained by the order of opinfo[] */ enum token { @@ -141,12 +141,6 @@ struct expr_state { (mksh_ari_t)((x)->val.u op (y)->val.u) : \ (mksh_ari_t)((x)->val.i op (y)->val.i) \ ) -#define chvui(x, op) do { \ - if (es->natural) \ - (x)->val.u = op (x)->val.u; \ - else \ - (x)->val.i = op (x)->val.i; \ -} while (/* CONSTCOND */ 0) #define stvui(x, n) do { \ if (es->natural) \ (x)->val.u = (n); \ @@ -269,26 +263,28 @@ evalerr(Expr_state *es, enum error_type type, const char *str) default: s = opinfo[(int)es->tok].name; } - warningf(true, "%s: unexpected '%s'", es->expression, s); + warningf(true, "%s: %s '%s'", es->expression, + "unexpected", s); break; case ET_BADLIT: - warningf(true, "%s: bad number '%s'", es->expression, str); + warningf(true, "%s: %s '%s'", es->expression, + "bad number", str); break; case ET_RECURSIVE: - warningf(true, "%s: expression recurses on parameter '%s'", - es->expression, str); + warningf(true, "%s: %s '%s'", es->expression, + "expression recurses on parameter", str); break; case ET_LVALUE: - warningf(true, "%s: %s requires lvalue", - es->expression, str); + warningf(true, "%s: %s %s", + es->expression, str, "requires lvalue"); break; case ET_RDONLY: - warningf(true, "%s: %s applied to read only variable", - es->expression, str); + warningf(true, "%s: %s %s", + es->expression, str, "applied to read only variable"); break; default: /* keep gcc happy */ @@ -313,11 +309,11 @@ evalexpr(Expr_state *es, int prec) exprtoken(es); vl = intvar(es, evalexpr(es, P_PRIMARY)); if (op == O_BNOT) - chvui(vl, ~); + vl->val.i = ~vl->val.i; else if (op == O_LNOT) - chvui(vl, !); + vl->val.i = !vl->val.i; else if (op == O_MINUS) - chvui(vl, -); + vl->val.i = -vl->val.i; /* op == O_PLUS is a no-op */ } else if (op == OPEN_PAREN) { exprtoken(es); @@ -503,7 +499,7 @@ exprtoken(Expr_state *es) for (; ksh_isalnux(c); c = *cp) cp++; if (c == '[') { - int len; + size_t len; len = array_ref_len(cp); if (len == 0) @@ -680,12 +676,12 @@ utf_widthadj(const char *src, const char **dst) return (width); } -int +size_t utf_mbswidth(const char *s) { - size_t len; + size_t len, width = 0; unsigned int wc; - int width = 0, cw; + int cw; if (!UTFMODE) return (strlen(s)); @@ -808,7 +804,7 @@ utf_wctomb(char *dst, unsigned int wc) * disclaims all warranties with regard to this software. */ -__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $"); +__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.10 2010/12/11 16:05:03 tg Exp $"); int utf_wcwidth(unsigned int c) @@ -817,54 +813,77 @@ utf_wcwidth(unsigned int c) unsigned short first; unsigned short last; } comb[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + /* Unicode 6.0.0 BMP */ + { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD }, + { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, + { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, { 0x0610, 0x061A }, + { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DD }, + { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0816, 0x0819 }, + { 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D }, + { 0x0859, 0x085B }, { 0x0900, 0x0902 }, { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 }, + { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, + { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, + { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, + { 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, + { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0C62, 0x0C63 }, + { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, + { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D44 }, + { 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 }, { 0x0DCA, 0x0DCA }, + { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, + { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, + { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, + { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, + { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, + { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC }, + { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 }, + { 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 }, + { 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 }, + { 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D }, + { 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 }, + { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, + { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D }, + { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, + { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, + { 0x1A56, 0x1A56 }, { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 }, + { 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C }, + { 0x1A7F, 0x1A7F }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB } + { 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 }, + { 0x1BA8, 0x1BA9 }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 }, + { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 }, + { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 }, + { 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED }, { 0x1DC0, 0x1DE6 }, + { 0x1DFC, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, + { 0x2060, 0x2064 }, { 0x206A, 0x206F }, { 0x20D0, 0x20F0 }, + { 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF }, + { 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 }, + { 0xA67C, 0xA67D }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 }, + { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, + { 0xA8C4, 0xA8C4 }, { 0xA8E0, 0xA8F1 }, { 0xA926, 0xA92D }, + { 0xA947, 0xA951 }, { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 }, + { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BC }, { 0xAA29, 0xAA2E }, + { 0xAA31, 0xAA32 }, { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 }, + { 0xAA4C, 0xAA4C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 }, + { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 }, + { 0xABE5, 0xABE5 }, { 0xABE8, 0xABE8 }, { 0xABED, 0xABED }, + { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE26 }, + { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB } }; size_t min = 0, mid, max = NELEM(comb) - 1; /* test for 8-bit control characters */ - if (c < 32 || (c >= 0x7f && c < 0xa0)) + if (c < 32 || (c >= 0x7F && c < 0xA0)) return (c ? -1 : 0); /* binary search in table of non-spacing characters */ @@ -880,16 +899,36 @@ utf_wcwidth(unsigned int c) } /* if we arrive here, c is not a combining or C0/C1 control char */ + return ((c >= 0x1100 && ( - c <= 0x115f || /* Hangul Jamo init. consonants */ - c == 0x2329 || c == 0x232a || - (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */ - (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */ - (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */ - (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */ - (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */ - (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */ - (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1); + c <= 0x115F || /* Hangul Jamo init. consonants */ + c == 0x2329 || c == 0x232A || + (c >= 0x2E80 && c <= 0xA4CF && c != 0x303F) || /* CJK ... Yi */ + (c >= 0xAC00 && c <= 0xD7A3) || /* Hangul Syllables */ + (c >= 0xF900 && c <= 0xFAFF) || /* CJK Compatibility Ideographs */ + (c >= 0xFE10 && c <= 0xFE19) || /* Vertical forms */ + (c >= 0xFE30 && c <= 0xFE6F) || /* CJK Compatibility Forms */ + (c >= 0xFF00 && c <= 0xFF60) || /* Fullwidth Forms */ + (c >= 0xFFE0 && c <= 0xFFE6))) ? 2 : 1); } /* --- end of wcwidth.c excerpt --- */ #endif + +/* + * Wrapper around access(2) because it says root can execute everything + * on some operating systems. Does not set errno, no user needs it. Use + * this iff mode can have the X_OK bit set, access otherwise. + */ +int +ksh_access(const char *fn, int mode) +{ + int rv; + struct stat sb; + + if ((rv = access(fn, mode)) == 0 && kshuid == 0 && (mode & X_OK) && + (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) && + (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) + rv = -1; + + return (rv); +} diff --git a/src/funcs.c b/src/funcs.c index 9d9c03a..23f45de 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -4,7 +4,8 @@ /* $OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $ */ /*- - * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, + * 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -25,7 +26,19 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $"); +#if HAVE_SELECT +#if HAVE_SYS_BSDTYPES_H +#include <sys/bsdtypes.h> +#endif +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#if HAVE_BSTRING_H +#include <bstring.h> +#endif +#endif + +__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.197 2011/09/07 15:24:15 tg Exp $"); #if HAVE_KILLPG /* @@ -40,51 +53,69 @@ __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $"); /* XXX conditions correct? */ #if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS) -#define MKSH_NO_LIMITS +#define MKSH_NO_LIMITS 1 #endif #ifdef MKSH_NO_LIMITS -#define c_ulimit c_label +#define c_ulimit c_true +#endif + +#if defined(ANDROID) +static int c_android_lsmod(const char **); #endif -extern uint8_t set_refflag; +static int +c_true(const char **wp MKSH_A_UNUSED) +{ + return (0); +} -/* A leading = means assignments before command are kept; +static int +c_false(const char **wp MKSH_A_UNUSED) +{ + return (1); +} + +/* + * A leading = means assignments before command are kept; * a leading * means a POSIX special builtin; * a leading + means a POSIX regular builtin * (* and + should not be combined). */ const struct builtin mkshbuiltins[] = { {"*=.", c_dot}, - {"*=:", c_label}, + {"*=:", c_true}, {"[", c_test}, {"*=break", c_brkcont}, - {"=builtin", c_builtin}, + {Tgbuiltin, c_builtin}, {"*=continue", c_brkcont}, {"*=eval", c_eval}, {"*=exec", c_exec}, {"*=exit", c_exitreturn}, - {"+false", c_label}, + {"+false", c_false}, {"*=return", c_exitreturn}, - {"*=set", c_set}, + {Tsgset, c_set}, {"*=shift", c_shift}, {"=times", c_times}, {"*=trap", c_trap}, {"+=wait", c_wait}, {"+read", c_read}, {"test", c_test}, - {"+true", c_label}, + {"+true", c_true}, {"ulimit", c_ulimit}, {"+umask", c_umask}, {"*=unset", c_unset}, - {"+alias", c_alias}, /* no =: AT&T manual wrong */ + /* no =: AT&T manual wrong */ + {Tpalias, c_alias}, {"+cd", c_cd}, - {"chdir", c_cd}, /* dash compatibility hack */ + /* dash compatibility hack */ + {"chdir", c_cd}, {"+command", c_command}, {"echo", c_print}, {"*=export", c_typeset}, {"+fc", c_fc}, {"+getopts", c_getopts}, + {"=global", c_typeset}, {"+jobs", c_jobs}, {"+kill", c_kill}, {"let", c_let}, @@ -94,19 +125,30 @@ const struct builtin mkshbuiltins[] = { #endif {"pwd", c_pwd}, {"*=readonly", c_typeset}, - {T__typeset, c_typeset}, - {"+unalias", c_unalias}, + {T_typeset, c_typeset}, + {Tpunalias, c_unalias}, {"whence", c_whence}, #ifndef MKSH_UNEMPLOYED {"+bg", c_fgbg}, {"+fg", c_fgbg}, #endif {"bind", c_bind}, + {"cat", c_cat}, #if HAVE_MKNOD {"mknod", c_mknod}, #endif {"realpath", c_realpath}, {"rename", c_rename}, +#if HAVE_SELECT + {"sleep", c_sleep}, +#endif +#ifdef __MirBSD__ + /* alias to "true" for historical reasons */ + {"domainname", c_true}, +#endif +#if defined(ANDROID) + {"lsmod", c_android_lsmod}, +#endif {NULL, (int (*)(const char **))NULL} }; @@ -163,7 +205,6 @@ static const struct t_op b_ops[] = { {"", TO_NONOP } }; -static int test_eaccess(const char *, int); static int test_oexpr(Test_env *, bool); static int test_aexpr(Test_env *, bool); static int test_nexpr(Test_env *, bool); @@ -171,331 +212,16 @@ static int test_primary(Test_env *, bool); static Test_op ptest_isa(Test_env *, Test_meta); static const char *ptest_getopnd(Test_env *, Test_op, bool); static void ptest_error(Test_env *, int, const char *); -static char *kill_fmt_entry(char *, int, int, const void *); +static char *kill_fmt_entry(char *, size_t, int, const void *); static void p_time(struct shf *, bool, long, int, int, const char *, const char *) - MKSH_A_NONNULL((nonnull (6, 7))); -static char *do_realpath(const char *); - -static char * -do_realpath(const char *upath) -{ - char *xp, *ip, *tp, *ipath, *ldest = NULL; - XString xs; - ptrdiff_t pos; - size_t len; - int symlinks = 32; /* max. recursion depth */ - int llen; - struct stat sb; -#ifdef NO_PATH_MAX - size_t ldestlen = 0; -#define pathlen sb.st_size -#define pathcnd (ldestlen < (pathlen + 1)) -#else -#define pathlen PATH_MAX -#define pathcnd (!ldest) -#endif - - if (upath[0] == '/') { - /* upath is an absolute pathname */ - strdupx(ipath, upath, ATEMP); - } else { - /* upath is a relative pathname, prepend cwd */ - if ((tp = ksh_get_wd(NULL)) == NULL || tp[0] != '/') - return (NULL); - ipath = shf_smprintf("%s/%s", tp, upath); - afree(tp, ATEMP); - } - - Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); - - while (*ip) { - /* skip slashes in input */ - while (*ip == '/') - ++ip; - if (!*ip) - break; - - /* get next pathname component from input */ - tp = ip; - while (*ip && *ip != '/') - ++ip; - len = ip - tp; - - /* check input for "." and ".." */ - if (tp[0] == '.') { - if (len == 1) - /* just continue with the next one */ - continue; - else if (len == 2 && tp[1] == '.') { - /* strip off last pathname component */ - while (xp > Xstring(xs, xp)) - if (*--xp == '/') - break; - /* then continue with the next one */ - continue; - } - } - - /* store output position away, then append slash to output */ - pos = Xsavepos(xs, xp); - /* 1 for the '/' and len + 1 for tp and the NUL from below */ - XcheckN(xs, xp, 1 + len + 1); - Xput(xs, xp, '/'); - - /* append next pathname component to output */ - memcpy(xp, tp, len); - xp += len; - *xp = '\0'; - - /* lstat the current output, see if it's a symlink */ - if (lstat(Xstring(xs, xp), &sb)) { - /* lstat failed */ - if (errno == ENOENT) { - /* because the pathname does not exist */ - while (*ip == '/') - /* skip any trailing slashes */ - ++ip; - /* no more components left? */ - if (!*ip) - /* we can still return successfully */ - break; - /* more components left? fall through */ - } - /* not ENOENT or not at the end of ipath */ - goto notfound; - } - - /* check if we encountered a symlink? */ - if (S_ISLNK(sb.st_mode)) { - /* reached maximum recursion depth? */ - if (!symlinks--) { - /* yep, prevent infinite loops */ - errno = ELOOP; - goto notfound; - } - - /* get symlink(7) target */ - if (pathcnd) - ldest = aresize(ldest, pathlen + 1, ATEMP); - llen = readlink(Xstring(xs, xp), ldest, pathlen); - if (llen < 0) - /* oops... */ - goto notfound; - ldest[llen] = '\0'; - - /* - * restart if symlink target is an absolute path, - * otherwise continue with currently resolved prefix - */ - xp = (ldest[0] == '/') ? Xstring(xs, xp) : - Xrestpos(xs, xp, pos); - tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip); - afree(ipath, ATEMP); - ip = ipath = tp; - } - /* otherwise (no symlink) merely go on */ - } - - /* - * either found the target and successfully resolved it, - * or found its parent directory and may create it - */ - if (Xlength(xs, xp) == 0) - /* - * if the resolved pathname is "", make it "/", - * otherwise do not add a trailing slash - */ - Xput(xs, xp, '/'); - Xput(xs, xp, '\0'); - - /* - * if source path had a trailing slash, check if target path - * is not a non-directory existing file - */ - if (ip > ipath && ip[-1] == '/') { - if (stat(Xstring(xs, xp), &sb)) { - if (errno != ENOENT) - goto notfound; - } else if (!S_ISDIR(sb.st_mode)) { - errno = ENOTDIR; - goto notfound; - } - /* target now either does not exist or is a directory */ - } - - /* return target path */ - if (ldest != NULL) - afree(ldest, ATEMP); - afree(ipath, ATEMP); - return (Xclose(xs, xp)); - - notfound: - llen = errno; /* save; free(3) might trash it */ - if (ldest != NULL) - afree(ldest, ATEMP); - afree(ipath, ATEMP); - Xfree(xs, xp); - errno = llen; - return (NULL); - -#undef pathlen -#undef pathcnd -} - -int -c_cd(const char **wp) -{ - int optc, rv, phys_path; - bool physical = Flag(FPHYSICAL) ? true : false; - int cdnode; /* was a node from cdpath added in? */ - bool printpath = false; /* print where we cd'd? */ - struct tbl *pwd_s, *oldpwd_s; - XString xs; - char *dir, *allocd = NULL, *tryp, *pwd, *cdpath; - - while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) - switch (optc) { - case 'L': - physical = false; - break; - case 'P': - physical = true; - break; - case '?': - return (1); - } - wp += builtin_opt.optind; - - if (Flag(FRESTRICTED)) { - bi_errorf("restricted shell - can't cd"); - return (1); - } - - pwd_s = global("PWD"); - oldpwd_s = global("OLDPWD"); - - if (!wp[0]) { - /* No arguments - go home */ - if ((dir = str_val(global("HOME"))) == null) { - bi_errorf("no home directory (HOME not set)"); - return (1); - } - } else if (!wp[1]) { - /* One argument: - or dir */ - strdupx(allocd, wp[0], ATEMP); - if (ksh_isdash((dir = allocd))) { - afree(allocd, ATEMP); - allocd = NULL; - dir = str_val(oldpwd_s); - if (dir == null) { - bi_errorf("no OLDPWD"); - return (1); - } - printpath = true; - } - } else if (!wp[2]) { - /* Two arguments - substitute arg1 in PWD for arg2 */ - int ilen, olen, nlen, elen; - char *cp; - - if (!current_wd[0]) { - bi_errorf("don't know current directory"); - return (1); - } - /* substitute arg1 for arg2 in current path. - * if the first substitution fails because the cd fails - * we could try to find another substitution. For now - * we don't - */ - if ((cp = strstr(current_wd, wp[0])) == NULL) { - bi_errorf("bad substitution"); - return (1); - } - ilen = cp - current_wd; - olen = strlen(wp[0]); - nlen = strlen(wp[1]); - elen = strlen(current_wd + ilen + olen) + 1; - dir = allocd = alloc(ilen + nlen + elen, ATEMP); - memcpy(dir, current_wd, ilen); - memcpy(dir + ilen, wp[1], nlen); - memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen); - printpath = true; - } else { - bi_errorf("too many arguments"); - return (1); - } - -#ifdef NO_PATH_MAX - /* only a first guess; make_path will enlarge xs if necessary */ - XinitN(xs, 1024, ATEMP); -#else - XinitN(xs, PATH_MAX, ATEMP); -#endif - - cdpath = str_val(global("CDPATH")); - do { - cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); - if (physical) - rv = chdir(tryp = Xstring(xs, xp) + phys_path); - else { - simplify_path(Xstring(xs, xp)); - rv = chdir(tryp = Xstring(xs, xp)); - } - } while (rv < 0 && cdpath != NULL); - - if (rv < 0) { - if (cdnode) - bi_errorf("%s: bad directory", dir); - else - bi_errorf("%s - %s", tryp, strerror(errno)); - afree(allocd, ATEMP); - return (1); - } - - /* allocd (above) => dir, which is no longer used */ - afree(allocd, ATEMP); - allocd = NULL; - - /* Clear out tracked aliases with relative paths */ - flushcom(0); - - /* Set OLDPWD (note: unsetting OLDPWD does not disable this - * setting in AT&T ksh) - */ - if (current_wd[0]) - /* Ignore failure (happens if readonly or integer) */ - setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); - - if (Xstring(xs, xp)[0] != '/') { - pwd = NULL; - } else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp)))) - pwd = Xstring(xs, xp); - - /* Set PWD */ - if (pwd) { - char *ptmp = pwd; - - set_current_wd(ptmp); - /* Ignore failure (happens if readonly or integer) */ - setstr(pwd_s, ptmp, KSH_RETURN_ERROR); - } else { - set_current_wd(null); - pwd = Xstring(xs, xp); - /* XXX unset $PWD? */ - } - if (printpath || cdnode) - shprintf("%s\n", pwd); - - afree(allocd, ATEMP); - return (0); -} + MKSH_A_NONNULL((__nonnull__ (6, 7))); int c_pwd(const char **wp) { int optc; - bool physical = Flag(FPHYSICAL) ? true : false; + bool physical = tobool(Flag(FPHYSICAL)); char *p, *allocd = NULL; while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) @@ -517,10 +243,12 @@ c_pwd(const char **wp) } p = current_wd[0] ? (physical ? allocd = do_realpath(current_wd) : current_wd) : NULL; + /* LINTED use of access */ if (p && access(p, R_OK) < 0) p = NULL; - if (!p && !(p = allocd = ksh_get_wd(NULL))) { - bi_errorf("can't get current directory - %s", strerror(errno)); + if (!p && !(p = allocd = ksh_get_wd())) { + bi_errorf("%s: %s", "can't determine current directory", + strerror(errno)); return (1); } shprintf("%s\n", p); @@ -549,7 +277,7 @@ c_print(const char **wp) if (wp[0][0] == 'e') { /* echo builtin */ wp++; - if (Flag(FPOSIX) || Flag(FSH)) { + if (Flag(FPOSIX) || Flag(FSH) || Flag(FAS_BUILTIN)) { /* Debian Policy 10.4 compliant "echo" builtin */ if (*wp && !strcmp(*wp, "-n")) { /* we recognise "-n" only as the first arg */ @@ -599,7 +327,8 @@ c_print(const char **wp) while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) switch (optc) { - case 'R': /* fake BSD echo command */ + case 'R': + /* fake BSD echo command */ flags |= PO_PMINUSMINUS; flags &= ~PO_EXPAND; opts = "ne"; @@ -612,7 +341,7 @@ c_print(const char **wp) break; case 'p': if ((fd = coproc_getfd(W_OK, &emsg)) < 0) { - bi_errorf("-p: %s", emsg); + bi_errorf("%s: %s", "-p", emsg); return (1); } break; @@ -626,7 +355,7 @@ c_print(const char **wp) if (!*(s = builtin_opt.optarg)) fd = 0; else if ((fd = check_fd(s, W_OK, &emsg)) < 0) { - bi_errorf("-u: %s: %s", s, emsg); + bi_errorf("%s: %s: %s", "-u", s, emsg); return (1); } break; @@ -672,8 +401,7 @@ c_print(const char **wp) /* generic function returned Unicode */ char ts[4]; - c = utf_wctomb(ts, c - 0x100); - ts[c] = 0; + ts[utf_wctomb(ts, c - 0x100)] = 0; for (c = 0; ts[c]; ++c) Xput(xs, xp, ts[c]); continue; @@ -695,7 +423,8 @@ c_print(const char **wp) int len = Xlength(xs, xp); int opipe = 0; - /* Ensure we aren't killed by a SIGPIPE while writing to + /* + * Ensure we aren't killed by a SIGPIPE while writing to * a coprocess. AT&T ksh doesn't seem to do this (seems * to just check that the co-process is alive which is * not enough). @@ -770,7 +499,8 @@ c_whence(const char **wp) /* Note that -p on its own is deal with in comexec() */ if (pflag) fcflags |= FC_DEFPATH; - /* Convert command options to whence options - note that + /* + * Convert command options to whence options - note that * command -pV uses a different path search than whence -v * or whence -pv. This should be considered a feature. */ @@ -795,22 +525,32 @@ c_whence(const char **wp) if (vflag || (tp->type != CALIAS && tp->type != CEXEC && tp->type != CTALIAS)) shf_puts(id, shl_stdout); + if (vflag) + switch (tp->type) { + case CKEYWD: + case CALIAS: + case CFUNC: + case CSHELL: + shf_puts(" is a", shl_stdout); + break; + } + switch (tp->type) { case CKEYWD: if (vflag) - shf_puts(" is a reserved word", shl_stdout); + shf_puts(" reserved word", shl_stdout); break; case CALIAS: if (vflag) - shprintf(" is an %salias for ", - (tp->flag & EXPORT) ? "exported " : null); + shprintf("n %s%s for ", + (tp->flag & EXPORT) ? "exported " : null, + Talias); if (!iam_whence && !vflag) - shprintf("alias %s=", id); + shprintf("%s %s=", Talias, id); print_value_quoted(tp->val.s); break; case CFUNC: if (vflag) { - shf_puts(" is a", shl_stdout); if (tp->flag & EXPORT) shf_puts("n exported", shl_stdout); if (tp->flag & TRACE) @@ -821,13 +561,14 @@ c_whence(const char **wp) shprintf(" (autoload from %s)", tp->u.fpath); } - shf_puts(" function", shl_stdout); + shf_puts(T_function, shl_stdout); } break; case CSHELL: if (vflag) - shprintf(" is a%s shell builtin", - (tp->flag & SPEC_BI) ? " special" : null); + shprintf("%s %s %s", + (tp->flag & SPEC_BI) ? " special" : null, + "shell", Tbuiltin); break; case CTALIAS: case CEXEC: @@ -835,14 +576,15 @@ c_whence(const char **wp) if (vflag) { shf_puts(" is ", shl_stdout); if (tp->type == CTALIAS) - shprintf("a tracked %salias for ", + shprintf("a tracked %s%s for ", (tp->flag & EXPORT) ? - "exported " : null); + "exported " : null, + Talias); } shf_puts(tp->val.s, shl_stdout); } else { if (vflag) - shf_puts(" not found", shl_stdout); + shprintf(" %s\n", "not found"); rv = 1; } break; @@ -860,37 +602,46 @@ c_whence(const char **wp) int c_command(const char **wp) { - /* Let c_whence do the work. Note that c_command() must be + /* + * Let c_whence do the work. Note that c_command() must be * a distinct function from c_whence() (tested in comexec()). */ return (c_whence(wp)); } -/* typeset, export, and readonly */ +/* typeset, global, export, and readonly */ int c_typeset(const char **wp) { struct block *l; struct tbl *vp, **p; - Tflag fset = 0, fclr = 0, flag; + uint32_t fset = 0, fclr = 0, flag; int thing = 0, field, base, optc; const char *opts; const char *fieldstr, *basestr; bool localv = false, func = false, pflag = false, istset = true; switch (**wp) { - case 'e': /* export */ + + /* export */ + case 'e': fset |= EXPORT; istset = false; break; - case 'r': /* readonly */ + + /* readonly */ + case 'r': fset |= RDONLY; istset = false; break; - case 's': /* set */ + + /* set */ + case 's': /* called with 'typeset -' */ break; - case 't': /* typeset */ + + /* typeset */ + case 't': localv = true; break; } @@ -900,7 +651,8 @@ c_typeset(const char **wp) fieldstr = basestr = NULL; builtin_opt.flags |= GF_PLUSOPT; - /* AT&T ksh seems to have 0-9 as options which are multiplied + /* + * AT&T ksh seems to have 0-9 as options which are multiplied * to get a number that is used with -L, -R, -Z or -i (eg, -1R2 * sets right justify in a field of 12). This allows options * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and @@ -920,7 +672,8 @@ c_typeset(const char **wp) fieldstr = builtin_opt.optarg; break; case 'U': - /* AT&T ksh uses u, but this conflicts with + /* + * AT&T ksh uses u, but this conflicts with * upper/lower case. If this option is changed, * need to change the -U below as well */ @@ -948,10 +701,11 @@ c_typeset(const char **wp) flag = LCASEV; break; case 'n': - set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1; + set_refflag = (builtin_opt.info & GI_PLUS) ? + SRF_DISABLE : SRF_ENABLE; break; + /* export, readonly: POSIX -p flag */ case 'p': - /* export, readonly: POSIX -p flag */ /* typeset: show values as well */ pflag = true; if (istset) @@ -964,7 +718,8 @@ c_typeset(const char **wp) flag = TRACE; break; case 'u': - flag = UCASEV_AL; /* upper case / autoload */ + /* upper case / autoload */ + flag = UCASEV_AL; break; case 'x': flag = EXPORT; @@ -998,29 +753,35 @@ c_typeset(const char **wp) builtin_opt.optind++; } - if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) { + if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || + set_refflag != SRF_NOP)) { bi_errorf("only -t, -u and -x options may be used with -f"); - set_refflag = 0; + set_refflag = SRF_NOP; return (1); } if (wp[builtin_opt.optind]) { - /* Take care of exclusions. + /* + * Take care of exclusions. * At this point, flags in fset are cleared in fclr and vice * versa. This property should be preserved. */ - if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ + if (fset & LCASEV) + /* LCASEV has priority over UCASEV_AL */ fset &= ~UCASEV_AL; - if (fset & LJUST) /* LJUST has priority over RJUST */ + if (fset & LJUST) + /* LJUST has priority over RJUST */ fset &= ~RJUST; - if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */ + if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { + /* -Z implies -ZR */ fset |= RJUST; fclr &= ~RJUST; } - /* Setting these attributes clears the others, unless they + /* + * Setting these attributes clears the others, unless they * are also set in this command */ if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | - INTEGER | INT_U | INT_L)) || set_refflag) + INTEGER | INT_U | INT_L)) || set_refflag != SRF_NOP) fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | INTEGER | INT_U | INT_L); } @@ -1035,7 +796,7 @@ c_typeset(const char **wp) for (i = builtin_opt.optind; wp[i]; i++) { if (func) { f = findfunc(wp[i], hash(wp[i]), - (fset&UCASEV_AL) ? true : false); + tobool(fset & UCASEV_AL)); if (!f) { /* AT&T ksh does ++rv: bogus */ rv = 1; @@ -1044,34 +805,38 @@ c_typeset(const char **wp) if (fset | fclr) { f->flag |= fset; f->flag &= ~fclr; - } else - fptreef(shl_stdout, 0, - f->flag & FKSH ? - "function %s %T\n" : - "%s() %T\n", wp[i], f->val.t); + } else { + fpFUNCTf(shl_stdout, 0, + tobool(f->flag & FKSH), + wp[i], f->val.t); + shf_putc('\n', shl_stdout); + } } else if (!typeset(wp[i], fset, fclr, field, base)) { - bi_errorf("%s: not identifier", wp[i]); - set_refflag = 0; + bi_errorf("%s: %s", wp[i], "not identifier"); + set_refflag = SRF_NOP; return (1); } } - set_refflag = 0; + set_refflag = SRF_NOP; return (rv); } /* list variables and attributes */ - flag = fset | fclr; /* no difference at this point.. */ + + /* no difference at this point.. */ + flag = fset | fclr; if (func) { for (l = e->loc; l; l = l->next) { for (p = ktsort(&l->funs); (vp = *p++); ) { if (flag && (vp->flag & flag) == 0) continue; if (thing == '-') - fptreef(shl_stdout, 0, vp->flag & FKSH ? - "function %s %T\n" : "%s() %T\n", + fpFUNCTf(shl_stdout, 0, + tobool(vp->flag & FKSH), vp->name, vp->val.t); else - shprintf("%s\n", vp->name); + shf_puts(vp->name, shl_stdout); + shf_putc('\n', shl_stdout); } } } else { @@ -1103,15 +868,18 @@ c_typeset(const char **wp) if (flag && (vp->flag & flag) == 0) continue; for (; vp; vp = vp->u.array) { - /* Ignore array elements that aren't + /* + * Ignore array elements that aren't * set unless there are no set elements, - * in which case the first is reported on */ + * in which case the first is reported on + */ if ((vp->flag&ARRAY) && any_set && !(vp->flag & ISSET)) continue; /* no arguments */ if (thing == 0 && flag == 0) { - /* AT&T ksh prints things + /* + * AT&T ksh prints things * like export, integer, * leftadj, zerofill, etc., * but POSIX says must @@ -1119,34 +887,36 @@ c_typeset(const char **wp) */ shf_puts("typeset ", shl_stdout); if (((vp->flag&(ARRAY|ASSOC))==ASSOC)) - shf_puts("-n ", shl_stdout); + shprintf("%s ", "-n"); if ((vp->flag&INTEGER)) - shf_puts("-i ", shl_stdout); + shprintf("%s ", "-i"); if ((vp->flag&EXPORT)) - shf_puts("-x ", shl_stdout); + shprintf("%s ", "-x"); if ((vp->flag&RDONLY)) - shf_puts("-r ", shl_stdout); + shprintf("%s ", "-r"); if ((vp->flag&TRACE)) - shf_puts("-t ", shl_stdout); + shprintf("%s ", "-t"); if ((vp->flag&LJUST)) shprintf("-L%d ", vp->u2.field); if ((vp->flag&RJUST)) shprintf("-R%d ", vp->u2.field); if ((vp->flag&ZEROFIL)) - shf_puts("-Z ", shl_stdout); + shprintf("%s ", "-Z"); if ((vp->flag&LCASEV)) - shf_puts("-l ", shl_stdout); + shprintf("%s ", "-l"); if ((vp->flag&UCASEV_AL)) - shf_puts("-u ", shl_stdout); + shprintf("%s ", "-u"); if ((vp->flag&INT_U)) - shf_puts("-U ", shl_stdout); + shprintf("%s ", "-U"); shf_puts(vp->name, shl_stdout); if (pflag) { char *s = str_val(vp); shf_putc('=', shl_stdout); - /* AT&T ksh can't have - * justified integers.. */ + /* + * AT&T ksh can't have + * justified integers... + */ if ((vp->flag & (INTEGER|LJUST|RJUST)) == INTEGER) @@ -1175,8 +945,10 @@ c_typeset(const char **wp) char *s = str_val(vp); shf_putc('=', shl_stdout); - /* AT&T ksh can't have - * justified integers.. */ + /* + * AT&T ksh can't have + * justified integers... + */ if ((vp->flag & (INTEGER|LJUST|RJUST)) == INTEGER) @@ -1186,7 +958,8 @@ c_typeset(const char **wp) } shf_putc('\n', shl_stdout); } - /* Only report first 'element' of an array with + /* + * Only report first 'element' of an array with * no set elements. */ if (!any_set) @@ -1204,7 +977,7 @@ c_alias(const char **wp) struct table *t = &aliases; int rv = 0, prefix = 0; bool rflag = false, tflag, Uflag = false, pflag = false; - Tflag xflag = 0; + uint32_t xflag = 0; int optc; builtin_opt.flags |= GF_PLUSOPT; @@ -1258,12 +1031,12 @@ c_alias(const char **wp) /* "hash -r" means reset all the tracked aliases.. */ if (rflag) { static const char *args[] = { - "unalias", "-ta", NULL + Tunalias, "-ta", NULL }; if (!tflag || *wp) { - shf_puts("alias: -r flag can only be used with -t" - " and without arguments\n", shl_stdout); + shprintf("%s: -r flag can only be used with -t" + " and without arguments\n", Talias); return (1); } ksh_getopt_reset(&builtin_opt, GF_ERROR); @@ -1276,7 +1049,7 @@ c_alias(const char **wp) for (p = ktsort(t); (ap = *p++) != NULL; ) if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) { if (pflag) - shf_puts("alias ", shl_stdout); + shprintf("%s ", Talias); shf_puts(ap->name, shl_stdout); if (prefix != '+') { shf_putc('=', shl_stdout); @@ -1301,7 +1074,7 @@ c_alias(const char **wp) ap = ktsearch(t, alias, h); if (ap != NULL && (ap->flag&ISSET)) { if (pflag) - shf_puts("alias ", shl_stdout); + shprintf("%s ", Talias); shf_puts(ap->name, shl_stdout); if (prefix != '+') { shf_putc('=', shl_stdout); @@ -1309,7 +1082,8 @@ c_alias(const char **wp) } shf_putc('\n', shl_stdout); } else { - shprintf("%s alias not found\n", alias); + shprintf("%s %s %s\n", alias, Talias, + "not found"); rv = 1; } continue; @@ -1323,7 +1097,9 @@ c_alias(const char **wp) afree(ap->val.s, APERM); } /* ignore values for -t (AT&T ksh does this) */ - newval = tflag ? search(alias, path, X_OK, NULL) : val; + newval = tflag ? + search_path(alias, path, X_OK, NULL) : + val; if (newval) { strdupx(ap->val.s, newval, APERM); ap->flag |= ALLOC|ISSET; @@ -1356,7 +1132,8 @@ c_unalias(const char **wp) break; case 'd': #ifdef MKSH_NOPWNAM - t = NULL; /* fix "unalias -dt" */ + /* fix "unalias -dt" */ + t = NULL; #else t = &homedirs; #endif @@ -1376,7 +1153,8 @@ c_unalias(const char **wp) for (; *wp != NULL; wp++) { ap = ktsearch(t, *wp, hash(*wp)); if (ap == NULL) { - rv = 1; /* POSIX */ + /* POSIX */ + rv = 1; continue; } if (ap->flag&ALLOC) { @@ -1407,12 +1185,14 @@ c_let(const char **wp) int rv = 1; mksh_ari_t val; - if (wp[1] == NULL) /* AT&T ksh does this */ + if (wp[1] == NULL) + /* AT&T ksh does this */ bi_errorf("no arguments"); else for (wp++; *wp; wp++) if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) { - rv = 2; /* distinguish error from zero result */ + /* distinguish error from zero result */ + rv = 2; break; } else rv = val == 0; @@ -1435,7 +1215,8 @@ c_jobs(const char **wp) case 'n': nflag = 1; break; - case 'z': /* debugging: print zombies */ + case 'z': + /* debugging: print zombies */ nflag = -1; break; case '?': @@ -1478,7 +1259,7 @@ c_fgbg(const char **wp) /* format a single kill item */ static char * -kill_fmt_entry(char *buf, int buflen, int i, const void *arg) +kill_fmt_entry(char *buf, size_t buflen, int i, const void *arg) { const struct kill_info *ki = (const struct kill_info *)arg; @@ -1549,7 +1330,8 @@ c_kill(const char **wp) shprintf("%d\n", n); } } else { - int w, j, mess_cols, mess_octs; + ssize_t w, mess_cols, mess_octs; + int j; struct kill_info ki; for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10) @@ -1582,8 +1364,8 @@ c_kill(const char **wp) if (j_kill(p, sig)) rv = 1; } else if (!getn(p, &n)) { - bi_errorf("%s: arguments must be jobs or process IDs", - p); + bi_errorf("%s: %s", p, + "arguments must be jobs or process IDs"); rv = 1; } else { if (mksh_kill(n, sig) < 0) { @@ -1618,22 +1400,22 @@ c_getopts(const char **wp) opts = *wp++; if (!opts) { - bi_errorf("missing options argument"); + bi_errorf("missing %s argument", "options"); return (1); } var = *wp++; if (!var) { - bi_errorf("missing name argument"); + bi_errorf("missing %s argument", "name"); return (1); } if (!*var || *skip_varname(var, true)) { - bi_errorf("%s: is not an identifier", var); + bi_errorf("%s: %s", var, "is not an identifier"); return (1); } if (e->loc->next == NULL) { - internal_warningf("c_getopts: no argv"); + internal_warningf("%s: %s", "c_getopts", "no argv"); return (1); } /* Which arguments are we parsing... */ @@ -1660,7 +1442,8 @@ c_getopts(const char **wp) buf[1] = optc; buf[2] = '\0'; } else { - /* POSIX says var is set to ? at end-of-options, AT&T ksh + /* + * POSIX says var is set to ? at end-of-options, AT&T ksh * sets it to null - we go with POSIX... */ buf[0] = optc < 0 ? '?' : optc; @@ -1671,7 +1454,8 @@ c_getopts(const char **wp) user_opt.uoptind = user_opt.optind; voptarg = global("OPTARG"); - voptarg->flag &= ~RDONLY; /* AT&T ksh clears ro and int */ + /* AT&T ksh clears ro and int */ + voptarg->flag &= ~RDONLY; /* Paranoia: ensure no bizarre results. */ if (voptarg->flag & INTEGER) typeset("OPTARG", 0, INTEGER, 0, 0); @@ -1686,7 +1470,7 @@ c_getopts(const char **wp) vq = global(var); /* Error message already printed (integer, readonly) */ if (!setstr(vq, buf, KSH_RETURN_ERROR)) - rv = 1; + rv = 2; if (Flag(FEXPORT)) typeset(var, EXPORT, 0, 0, 0); @@ -1725,7 +1509,8 @@ c_bind(const char **wp) } wp += builtin_opt.optind; - if (*wp == NULL) /* list all */ + if (*wp == NULL) + /* list all */ rv = x_bind(NULL, NULL, #ifndef MKSH_SMALL false, @@ -1751,13 +1536,6 @@ c_bind(const char **wp) return (rv); } -/* :, false and true (and ulimit if MKSH_NO_LIMITS) */ -int -c_label(const char **wp) -{ - return (wp[0][0] == 'f' ? 1 : 0); -} - int c_shift(const char **wp) { @@ -1776,7 +1554,7 @@ c_shift(const char **wp) } else n = 1; if (n < 0) { - bi_errorf("%s: bad number", arg); + bi_errorf("%s: %s", arg, "bad number"); return (1); } if (l->argc < n) { @@ -1843,7 +1621,8 @@ c_umask(const char **wp) char op; old_umask = umask((mode_t)0); - umask(old_umask); /* in case of error */ + /* in case of error */ + umask(old_umask); old_umask = ~old_umask; new_umask = old_umask; positions = 0; @@ -1864,7 +1643,8 @@ c_umask(const char **wp) break; } if (!positions) - positions = 0111; /* default is a */ + /* default is a */ + positions = 0111; if (!vstrchr("=+-", op = *cp)) break; cp++; @@ -1933,16 +1713,16 @@ c_dot(const char **wp) bi_errorf("missing argument"); return (1); } - if ((file = search(cp, path, R_OK, &errcode)) == NULL) { - bi_errorf("%s: %s", cp, - errcode ? strerror(errcode) : "not found"); + if ((file = search_path(cp, path, R_OK, &errcode)) == NULL) { + bi_errorf("%s: %s", cp, strerror(errcode)); return (1); } /* Set positional parameters? */ if (wp[builtin_opt.optind + 1]) { argv = wp + builtin_opt.optind; - argv[0] = e->loc->argv[0]; /* preserve $0 */ + /* preserve $0 */ + argv[0] = e->loc->argv[0]; for (argc = 0; argv[argc + 1]; argc++) ; } else { @@ -1973,7 +1753,8 @@ c_wait(const char **wp) for (; *wp; wp++) rv = waitfor(*wp, &sig); if (rv < 0) - rv = sig ? sig : 127; /* magic exit code: bad job-id */ + /* magic exit code: bad job-id */ + rv = sig ? sig : 127; } return (rv); } @@ -1981,175 +1762,399 @@ c_wait(const char **wp) int c_read(const char **wp) { - int c = 0, ecode = 0, fd = 0, optc; - bool expande = true, historyr = false, expanding; - const char *cp, *emsg; - struct shf *shf; - XString cs, xs = { NULL, NULL, 0, NULL}; - struct tbl *vp; - char *ccp, *xp = NULL, *wpalloc = NULL; +#define is_ifsws(c) (ctype((c), C_IFS) && ctype((c), C_IFSWS)) static char REPLY[] = "REPLY"; + int c, fd = 0, rv = 0, lastparm = 0; + bool savehist = false, intoarray = false, aschars = false; + bool rawmode = false, expanding = false; + enum { LINES, BYTES, UPTO, READALL } readmode = LINES; + char delim = '\n'; + size_t bytesleft = 128, bytesread; + struct tbl *vp /* FU gcc */ = NULL, *vq; + char *cp, *allocd = NULL, *xp; + const char *ccp; + XString xs; + ptrdiff_t xsave = 0; + struct termios tios; + bool restore_tios = false; +#if HAVE_SELECT + bool hastimeout = false; + struct timeval tv, tvlim; +#define c_read_opts "Aad:N:n:prst:u," +#else +#define c_read_opts "Aad:N:n:prsu," +#endif - while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1) - switch (optc) { - case 'p': - if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { - bi_errorf("-p: %s", emsg); - return (1); - } - break; - case 'r': - expande = false; - break; - case 's': - historyr = true; - break; - case 'u': - if (!*(cp = builtin_opt.optarg)) - fd = 0; - else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) { - bi_errorf("-u: %s: %s", cp, emsg); - return (1); - } - break; - case '?': - return (1); + while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1) + switch (c) { + case 'a': + aschars = true; + /* FALLTHROUGH */ + case 'A': + intoarray = true; + break; + case 'd': + delim = builtin_opt.optarg[0]; + break; + case 'N': + case 'n': + readmode = c == 'N' ? BYTES : UPTO; + if (!bi_getn(builtin_opt.optarg, &c)) + return (2); + if (c == -1) { + readmode = READALL; + bytesleft = 1024; + } else + bytesleft = (unsigned int)c; + break; + case 'p': + if ((fd = coproc_getfd(R_OK, &ccp)) < 0) { + bi_errorf("%s: %s", "-p", ccp); + return (2); } + break; + case 'r': + rawmode = true; + break; + case 's': + savehist = true; + break; +#if HAVE_SELECT + case 't': + if (parse_usec(builtin_opt.optarg, &tv)) { + bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno), + builtin_opt.optarg); + return (2); + } + hastimeout = true; + break; +#endif + case 'u': + if (!builtin_opt.optarg[0]) + fd = 0; + else if ((fd = check_fd(builtin_opt.optarg, R_OK, &ccp)) < 0) { + bi_errorf("%s: %s: %s", "-u", builtin_opt.optarg, ccp); + return (2); + } + break; + case '?': + return (2); + } wp += builtin_opt.optind; - if (*wp == NULL) *--wp = REPLY; - /* Since we can't necessarily seek backwards on non-regular files, - * don't buffer them so we can't read too much. - */ - shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare); + if (intoarray && wp[1] != NULL) { + bi_errorf("too many arguments"); + return (2); + } - if ((cp = cstrchr(*wp, '?')) != NULL) { - strdupx(wpalloc, *wp, ATEMP); - wpalloc[cp - *wp] = '\0'; - *wp = wpalloc; + if ((ccp = cstrchr(*wp, '?')) != NULL) { + strdupx(allocd, *wp, ATEMP); + allocd[ccp - *wp] = '\0'; + *wp = allocd; if (isatty(fd)) { - /* AT&T ksh says it prints prompt on fd if it's open + /* + * AT&T ksh says it prints prompt on fd if it's open * for writing and is a tty, but it doesn't do it * (it also doesn't check the interactive flag, - * as is indicated in the Kornshell book). + * as is indicated in the Korn Shell book). */ - shellf("%s", cp+1); + shf_puts(ccp + 1, shl_out); + shf_flush(shl_out); } } - /* If we are reading from the co-process for the first time, - * make sure the other side of the pipe is closed first. This allows - * the detection of eof. - * - * This is not compatible with AT&T ksh... the fd is kept so another - * coproc can be started with same output, however, this means eof - * can't be detected... This is why it is closed here. - * If this call is removed, remove the eof check below, too. - * coproc_readw_close(fd); - */ + Xinit(xs, xp, bytesleft, ATEMP); - if (historyr) - Xinit(xs, xp, 128, ATEMP); - expanding = false; - Xinit(cs, ccp, 128, ATEMP); - for (; *wp != NULL; wp++) { - for (ccp = Xstring(cs, ccp); ; ) { - if (c == '\n' || c == EOF) - break; - while (1) { - c = shf_getc(shf); - if (c == '\0') - continue; - if (c == EOF && shf_error(shf) && - shf_errno(shf) == EINTR) { - /* Was the offending signal one that - * would normally kill a process? - * If so, pretend the read was killed. - */ - ecode = fatal_trap_check(); + if (readmode == LINES) + bytesleft = 1; + else if (isatty(fd)) { + x_mkraw(fd, &tios, true); + restore_tios = true; + } - /* non fatal (eg, CHLD), carry on */ - if (!ecode) { - shf_clearerr(shf); - continue; - } +#if HAVE_SELECT + if (hastimeout) { + gettimeofday(&tvlim, NULL); + timeradd(&tvlim, &tv, &tvlim); + } +#endif + + c_read_readloop: +#if HAVE_SELECT + if (hastimeout) { + fd_set fdset; + + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + gettimeofday(&tv, NULL); + timersub(&tvlim, &tv, &tv); + if (tv.tv_sec < 0) { + /* timeout expired globally */ + rv = 1; + goto c_read_out; + } + + switch (select(fd + 1, &fdset, NULL, NULL, &tv)) { + case 1: + break; + case 0: + /* timeout expired for this call */ + rv = 1; + goto c_read_out; + default: + bi_errorf("%s: %s", Tselect, strerror(errno)); + rv = 2; + goto c_read_out; + } + } +#endif + + bytesread = blocking_read(fd, xp, bytesleft); + if (bytesread == (size_t)-1) { + /* interrupted */ + if (errno == EINTR && fatal_trap_check()) { + /* + * Was the offending signal one that would + * normally kill a process? If so, pretend + * the read was killed. + */ + rv = 2; + goto c_read_out; + } + /* just ignore the signal */ + goto c_read_readloop; + } + + switch (readmode) { + case READALL: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + goto c_read_readdone; + } + xp += bytesread; + XcheckN(xs, xp, bytesleft); + break; + + case UPTO: + if (bytesread == 0) + /* end of file reached */ + rv = 1; + xp += bytesread; + goto c_read_readdone; + + case BYTES: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + xp = Xstring(xs, xp); + goto c_read_readdone; + } + xp += bytesread; + if ((bytesleft -= bytesread) == 0) + goto c_read_readdone; + break; + case LINES: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + goto c_read_readdone; + } + if ((c = *xp) == '\0' && !aschars && delim != '\0') { + /* skip any read NULs unless delimiter */ + break; + } + if (expanding) { + expanding = false; + if (c == delim) { + if (Flag(FTALKING_I) && isatty(fd)) { + /* + * set prompt in case this is + * called from .profile or $ENV + */ + set_prompt(PS2, NULL); + pprompt(prompt, 0); } + /* drop the backslash */ + --xp; + /* and the delimiter */ break; } - if (historyr) { - Xcheck(xs, xp); - Xput(xs, xp, c); - } - Xcheck(cs, ccp); - if (expanding) { - expanding = false; - if (c == '\n') { - c = 0; - if (Flag(FTALKING_I) && isatty(fd)) { - /* set prompt in case this is - * called from .profile or $ENV - */ - set_prompt(PS2, NULL); - pprompt(prompt, 0); - } - } else if (c != EOF) - Xput(cs, ccp, c); - continue; - } - if (expande && c == '\\') { - expanding = true; - continue; - } - if (c == '\n' || c == EOF) - break; - if (ctype(c, C_IFS)) { - if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS)) - continue; - if (wp[1]) - break; - } - Xput(cs, ccp, c); - } - /* strip trailing IFS white space from last variable */ - if (!wp[1]) - while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) && - ctype(ccp[-1], C_IFSWS)) - ccp--; - Xput(cs, ccp, '\0'); + } else if (c == delim) { + goto c_read_readdone; + } else if (!rawmode && c == '\\') { + expanding = true; + } + Xcheck(xs, xp); + ++xp; + break; + } + goto c_read_readloop; + + c_read_readdone: + bytesread = Xlength(xs, xp); + Xput(xs, xp, '\0'); + + /*- + * state: we finished reading the input and NUL terminated it + * Xstring(xs, xp) -> xp-1 = input string without trailing delim + * rv = 1 if EOF, 0 otherwise (errors handled already) + */ + + if (rv == 1) { + /* clean up coprocess if needed, on EOF */ + coproc_read_close(fd); + if (readmode == READALL) + /* EOF is no error here */ + rv = 0; + } + + if (savehist) + histsave(&source->line, Xstring(xs, xp), true, false); + + ccp = cp = Xclose(xs, xp); + expanding = false; + XinitN(xs, 128, ATEMP); + if (intoarray) { vp = global(*wp); - /* Must be done before setting export. */ if (vp->flag & RDONLY) { - shf_flush(shf); - bi_errorf("%s is read only", *wp); - afree(wpalloc, ATEMP); - return (1); + c_read_splitro: + bi_errorf("%s: %s", *wp, "is read only"); + c_read_spliterr: + rv = 2; + afree(cp, ATEMP); + goto c_read_out; + } + /* exporting an array is currently pointless */ + unset(vp, 1); + /* counter for array index */ + c = 0; + } + if (!aschars) { + /* skip initial IFS whitespace */ + while (bytesread && is_ifsws(*ccp)) { + ++ccp; + --bytesread; + } + /* trim trailing IFS whitespace */ + while (bytesread && is_ifsws(ccp[bytesread - 1])) { + --bytesread; + } + } + c_read_splitloop: + xp = Xstring(xs, xp); + /* generate next word */ + if (!bytesread) { + /* no more input */ + if (intoarray) + goto c_read_splitdone; + /* zero out next parameters */ + goto c_read_gotword; + } + if (aschars) { + Xput(xs, xp, '1'); + Xput(xs, xp, '#'); + bytesleft = utf_ptradj(ccp); + while (bytesleft && bytesread) { + *xp++ = *ccp++; + --bytesleft; + --bytesread; + } + if (xp[-1] == '\0') { + xp[-1] = '0'; + xp[-3] = '2'; + } + goto c_read_gotword; + } + + if (!intoarray && wp[1] == NULL) + lastparm = 1; + + c_read_splitlast: + /* copy until IFS character */ + while (bytesread) { + char ch; + + ch = *ccp; + if (expanding) { + expanding = false; + goto c_read_splitcopy; + } else if (ctype(ch, C_IFS)) { + break; + } else if (!rawmode && ch == '\\') { + expanding = true; + } else { + c_read_splitcopy: + Xcheck(xs, xp); + Xput(xs, xp, ch); } + ++ccp; + --bytesread; + } + xsave = Xsavepos(xs, xp); + /* copy word delimiter: IFSWS+IFS,IFSWS */ + while (bytesread) { + char ch; + + ch = *ccp; + if (!ctype(ch, C_IFS)) + break; + Xcheck(xs, xp); + Xput(xs, xp, ch); + ++ccp; + --bytesread; + if (!ctype(ch, C_IFSWS)) + break; + } + while (bytesread && is_ifsws(*ccp)) { + Xcheck(xs, xp); + Xput(xs, xp, *ccp); + ++ccp; + --bytesread; + } + /* if no more parameters, rinse and repeat */ + if (lastparm && bytesread) { + ++lastparm; + goto c_read_splitlast; + } + /* get rid of the delimiter unless we pack the rest */ + if (lastparm < 2) + xp = Xrestpos(xs, xp, xsave); + c_read_gotword: + Xput(xs, xp, '\0'); + if (intoarray) { + vq = arraysearch(vp, c++); + } else { + vq = global(*wp); + /* must be checked before exporting */ + if (vq->flag & RDONLY) + goto c_read_splitro; if (Flag(FEXPORT)) typeset(*wp, EXPORT, 0, 0, 0); - if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) { - shf_flush(shf); - afree(wpalloc, ATEMP); - return (1); - } } - - shf_flush(shf); - if (historyr) { - Xput(xs, xp, '\0'); - histsave(&source->line, Xstring(xs, xp), true, false); - Xfree(xs, xp); + if (!setstr(vq, Xstring(xs, xp), KSH_RETURN_ERROR)) + goto c_read_spliterr; + if (aschars) { + setint_v(vq, vq, false); + /* protect from UTFMODE changes */ + vq->type = 0; } - /* if this is the co-process fd, close the file descriptor - * (can get eof if and only if all processes are have died, ie, - * coproc.njobs is 0 and the pipe is closed). - */ - if (c == EOF && !ecode) - coproc_read_close(fd); + if (intoarray || *++wp != NULL) + goto c_read_splitloop; + + c_read_splitdone: + /* free up */ + afree(cp, ATEMP); - afree(wpalloc, ATEMP); - return (ecode ? ecode : c == EOF); + c_read_out: + afree(allocd, ATEMP); + Xfree(xs, xp); + if (restore_tios) + tcsetattr(fd, TCSADRAIN, &tios); + return (rv); +#undef is_ifsws } int @@ -2231,20 +2236,21 @@ c_trap(const char **wp) * command 'exit' isn't confused with the pseudo-signal * 'EXIT'. */ - s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */ + /* get command */ + s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; if (s != NULL && s[0] == '-' && s[1] == '\0') s = NULL; /* set/clear traps */ - while (*wp != NULL) { - p = gettrap(*wp++, true); - if (p == NULL) { - bi_errorf("bad signal %s", wp[-1]); - return (1); - } - settrap(p, s); - } - return (0); + i = 0; + while (*wp != NULL) + if ((p = gettrap(*wp++, true)) == NULL) { + warningf(true, "%s: %s '%s'", builtin_argv0, + "bad signal", wp[-1]); + ++i; + } else + settrap(p, s); + return (i); } int @@ -2260,14 +2266,17 @@ c_exitreturn(const char **wp) if (arg) { if (!getn(arg, &n)) { exstat = 1; - warningf(true, "%s: bad number", arg); + warningf(true, "%s: %s", arg, "bad number"); } else exstat = n; - } - if (wp[0][0] == 'r') { /* return */ + } else if (trap_exstat != -1) + exstat = trap_exstat; + if (wp[0][0] == 'r') { + /* return */ struct env *ep; - /* need to tell if this is exit or return so trap exit will + /* + * need to tell if this is exit or return so trap exit will * work right (POSIX) */ for (ep = e; ep; ep = ep->oenv) @@ -2282,7 +2291,8 @@ c_exitreturn(const char **wp) how = LSHELL; } - quitenv(NULL); /* get rid of any i/o redirections */ + /* get rid of any i/o redirections */ + quitenv(NULL); unwind(how); /* NOTREACHED */ } @@ -2305,7 +2315,7 @@ c_brkcont(const char **wp) quit = n; if (quit <= 0) { /* AT&T ksh does this for non-interactive shells only - weird */ - bi_errorf("%s: bad value", arg); + bi_errorf("%s: %s", arg, "bad value"); return (1); } @@ -2319,15 +2329,17 @@ c_brkcont(const char **wp) } if (quit) { - /* AT&T ksh doesn't print a message - just does what it + /* + * AT&T ksh doesn't print a message - just does what it * can. We print a message 'cause it helps in debugging * scripts, but don't generate an error (ie, keep going). */ if (n == quit) { - warningf(true, "%s: cannot %s", wp[0], wp[0]); + warningf(true, "%s: %s %s", wp[0], "can't", wp[0]); return (0); } - /* POSIX says if n is too big, the last enclosing loop + /* + * POSIX says if n is too big, the last enclosing loop * shall be used. Doesn't say to print an error but we * do anyway 'cause the user messed up. */ @@ -2350,7 +2362,7 @@ c_set(const char **wp) const char **owp; if (wp[1] == NULL) { - static const char *args[] = { "set", "-", NULL }; + static const char *args[] = { Tset, "-", NULL }; return (c_typeset(args)); } @@ -2361,11 +2373,12 @@ c_set(const char **wp) if (setargs) { wp += argi - 1; owp = wp; - wp[0] = l->argv[0]; /* save $0 */ + /* save $0 */ + wp[0] = l->argv[0]; while (*++wp != NULL) strdupx(*wp, *wp, &l->area); l->argc = wp - owp - 1; - l->argv = alloc((l->argc + 2) * sizeof(char *), &l->area); + l->argv = alloc2(l->argc + 2, sizeof(char *), &l->area); for (wp = l->argv; (*wp++ = *owp++) != NULL; ) ; } @@ -2385,7 +2398,7 @@ int c_unset(const char **wp) { const char *id; - int optc; + int optc, rv = 0; bool unset_var = true; while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1) @@ -2397,11 +2410,13 @@ c_unset(const char **wp) unset_var = true; break; case '?': - return (1); + /*XXX not reached due to GF_ERROR */ + return (2); } wp += builtin_opt.optind; for (; (id = *wp) != NULL; wp++) - if (unset_var) { /* unset variable */ + if (unset_var) { + /* unset variable */ struct tbl *vp; char *cp = NULL; size_t n; @@ -2419,13 +2434,15 @@ c_unset(const char **wp) afree(cp, ATEMP); if ((vp->flag&RDONLY)) { - bi_errorf("%s is read only", vp->name); - return (1); - } - unset(vp, optc); - } else /* unset function */ + warningf(true, "%s: %s", vp->name, + "is read only"); + rv = 1; + } else + unset(vp, optc); + } else + /* unset function */ define(id, NULL); - return (0); + return (rv); } static void @@ -2497,7 +2514,8 @@ timex(struct op *t, int f, volatile int *xerrok) } else tf = TF_NOARGS; - if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */ + if (tf & TF_NOARGS) { + /* ksh93 - report shell times (shell+kids) */ tf |= TF_NOREAL; timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime); timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime); @@ -2542,17 +2560,19 @@ timex_hook(struct op *t, char **volatile *app) Getopt opt; ksh_getopt_reset(&opt, 0); - opt.optind = 0; /* start at the start */ + /* start at the start */ + opt.optind = 0; while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1) switch (optc) { case 'p': t->str[0] |= TF_POSIX; break; case '?': - errorf("time: -%s unknown option", opt.optarg); + errorf("time: -%s %s", opt.optarg, + "unknown option"); case ':': - errorf("time: -%s requires an argument", - opt.optarg); + errorf("time: -%s %s", opt.optarg, + "requires an argument"); } /* Copy command words down over options. */ if (opt.optind != 0) { @@ -2609,7 +2629,7 @@ c_mknod(const char **wp) return (1); } mode = getmode(set, (mode_t)(DEFFILEMODE)); - free(set); + free_ossetmode(set); break; default: goto c_mknod_usage; @@ -2640,28 +2660,28 @@ c_mknod(const char **wp) majnum = strtoul(argv[2], &c, 0); if ((c == argv[2]) || (*c != '\0')) { - bi_errorf("non-numeric device major '%s'", argv[2]); + bi_errorf("non-numeric %s %s '%s'", "device", "major", argv[2]); goto c_mknod_err; } minnum = strtoul(argv[3], &c, 0); if ((c == argv[3]) || (*c != '\0')) { - bi_errorf("non-numeric device minor '%s'", argv[3]); + bi_errorf("non-numeric %s %s '%s'", "device", "minor", argv[3]); goto c_mknod_err; } dv = makedev(majnum, minnum); if ((unsigned long)(major(dv)) != majnum) { - bi_errorf("device major too large: %lu", majnum); + bi_errorf("%s %s too large: %lu", "device", "major", majnum); goto c_mknod_err; } if ((unsigned long)(minor(dv)) != minnum) { - bi_errorf("device minor too large: %lu", minnum); + bi_errorf("%s %s too large: %lu", "device", "minor", minnum); goto c_mknod_err; } if (mknod(argv[0], mode, dv)) goto c_mknod_failed; } else if (mkfifo(argv[0], mode)) { c_mknod_failed: - bi_errorf("%s: %s", *wp, strerror(errno)); + bi_errorf("%s: %s", argv[0], strerror(errno)); c_mknod_err: rv = 1; } @@ -2670,20 +2690,14 @@ c_mknod(const char **wp) umask(oldmode); return (rv); c_mknod_usage: - bi_errorf("usage: mknod [-m mode] name b|c major minor"); - bi_errorf("usage: mknod [-m mode] name p"); + bi_errorf("%s: %s", "usage", "mknod [-m mode] name b|c major minor"); + bi_errorf("%s: %s", "usage", "mknod [-m mode] name p"); return (1); } #endif -/* dummy function, special case in comexec() */ -int -c_builtin(const char **wp MKSH_A_UNUSED) -{ - return (0); -} - -/* test(1) accepts the following grammar: +/*- + test(1) accepts the following grammar: oexpr ::= aexpr | aexpr "-o" oexpr ; aexpr ::= nexpr | nexpr "-a" aexpr ; nexpr ::= primary | "!" nexpr ; @@ -2704,7 +2718,8 @@ c_builtin(const char **wp MKSH_A_UNUSED) operand ::= <any thing> */ -#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ +/* POSIX says > 1 for errors */ +#define T_ERR_EXIT 2 int c_test(const char **wp) @@ -2737,11 +2752,23 @@ c_test(const char **wp) * our parser does the right thing for the omitted steps. */ if (argc <= 5) { - const char **owp = wp; + const char **owp = wp, **owpend = te.wp_end; int invert = 0; Test_op op; const char *opnd1, *opnd2; + if (argc >= 2 && ((*te.isa)(&te, TM_OPAREN))) { + te.pos.wp = te.wp_end - 1; + if ((*te.isa)(&te, TM_CPAREN)) { + argc -= 2; + te.wp_end--; + te.pos.wp = owp + 2; + } else { + te.pos.wp = owp + 1; + te.wp_end = owpend; + } + } + while (--argc >= 0) { if ((*te.isa)(&te, TM_END)) return (!0); @@ -2762,8 +2789,6 @@ c_test(const char **wp) } if (argc == 1) { opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); - if (strcmp(opnd1, "-t") == 0) - break; res = (*te.eval)(&te, TO_STNZE, opnd1, NULL, 1); if (invert & 1) @@ -2776,6 +2801,7 @@ c_test(const char **wp) break; } te.pos.wp = owp + 1; + te.wp_end = owpend; } return (test_parse(&te)); @@ -2813,99 +2839,184 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, if (!do_eval) return (0); - switch ((int)op) { + switch (op) { + /* * Unary Operators */ - case TO_STNZE: /* -n */ + + /* -n */ + case TO_STNZE: return (*opnd1 != '\0'); - case TO_STZER: /* -z */ + + /* -z */ + case TO_STZER: return (*opnd1 == '\0'); - case TO_OPTION: /* -o */ + + /* -o */ + case TO_OPTION: if ((i = *opnd1) == '!' || i == '?') opnd1++; if ((k = option(opnd1)) == (size_t)-1) return (0); return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k)); - case TO_FILRD: /* -r */ - return (test_eaccess(opnd1, R_OK) == 0); - case TO_FILWR: /* -w */ - return (test_eaccess(opnd1, W_OK) == 0); - case TO_FILEX: /* -x */ - return (test_eaccess(opnd1, X_OK) == 0); - case TO_FILAXST: /* -a */ - case TO_FILEXST: /* -e */ + + /* -r */ + case TO_FILRD: + /* LINTED use of access */ + return (access(opnd1, R_OK) == 0); + + /* -w */ + case TO_FILWR: + /* LINTED use of access */ + return (access(opnd1, W_OK) == 0); + + /* -x */ + case TO_FILEX: + return (ksh_access(opnd1, X_OK) == 0); + + /* -a */ + case TO_FILAXST: + /* -e */ + case TO_FILEXST: return (stat(opnd1, &b1) == 0); - case TO_FILREG: /* -r */ + + /* -r */ + case TO_FILREG: return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode)); - case TO_FILID: /* -d */ + + /* -d */ + case TO_FILID: return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode)); - case TO_FILCDEV: /* -c */ + + /* -c */ + case TO_FILCDEV: return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode)); - case TO_FILBDEV: /* -b */ + + /* -b */ + case TO_FILBDEV: return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode)); - case TO_FILFIFO: /* -p */ + + /* -p */ + case TO_FILFIFO: return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode)); - case TO_FILSYM: /* -h -L */ + + /* -h or -L */ + case TO_FILSYM: return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode)); - case TO_FILSOCK: /* -S */ + + /* -S */ + case TO_FILSOCK: return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode)); - case TO_FILCDF:/* -H HP context dependent files (directories) */ + + /* -H => HP context dependent files (directories) */ + case TO_FILCDF: +#ifdef S_ISCDF + { + char *nv; + + /* + * Append a + to filename and check to see if result is + * a setuid directory. CDF stuff in general is hookey, + * since it breaks for, e.g., the following sequence: + * echo hi >foo+; mkdir foo; echo bye >foo/default; + * chmod u+s foo (foo+ refers to the file with hi in it, + * there is no way to get at the file with bye in it; + * please correct me if I'm wrong about this). + */ + + nv = shf_smprintf("%s+", opnd1); + i = (stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode)); + afree(nv, ATEMP); + return (i); + } +#else return (0); - case TO_FILSETU: /* -u */ +#endif + + /* -u */ + case TO_FILSETU: return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISUID) == S_ISUID); - case TO_FILSETG: /* -g */ + + /* -g */ + case TO_FILSETG: return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISGID) == S_ISGID); - case TO_FILSTCK: /* -k */ + + /* -k */ + case TO_FILSTCK: #ifdef S_ISVTX return (stat(opnd1, &b1) == 0 && (b1.st_mode & S_ISVTX) == S_ISVTX); #else return (0); #endif - case TO_FILGZ: /* -s */ + + /* -s */ + case TO_FILGZ: return (stat(opnd1, &b1) == 0 && b1.st_size > 0L); - case TO_FILTT: /* -t */ + + /* -t */ + case TO_FILTT: if (opnd1 && !bi_getn(opnd1, &i)) { te->flags |= TEF_ERROR; i = 0; } else i = isatty(opnd1 ? i : 0); return (i); - case TO_FILUID: /* -O */ + + /* -O */ + case TO_FILUID: return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid); - case TO_FILGID: /* -G */ + + /* -G */ + case TO_FILGID: return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid()); + /* * Binary Operators */ - case TO_STEQL: /* = */ + + /* = */ + case TO_STEQL: if (te->flags & TEF_DBRACKET) return (gmatchx(opnd1, opnd2, false)); return (strcmp(opnd1, opnd2) == 0); - case TO_STNEQ: /* != */ + + /* != */ + case TO_STNEQ: if (te->flags & TEF_DBRACKET) return (!gmatchx(opnd1, opnd2, false)); return (strcmp(opnd1, opnd2) != 0); - case TO_STLT: /* < */ + + /* < */ + case TO_STLT: return (strcmp(opnd1, opnd2) < 0); - case TO_STGT: /* > */ + + /* > */ + case TO_STGT: return (strcmp(opnd1, opnd2) > 0); - case TO_INTEQ: /* -eq */ - case TO_INTNE: /* -ne */ - case TO_INTGE: /* -ge */ - case TO_INTGT: /* -gt */ - case TO_INTLE: /* -le */ - case TO_INTLT: /* -lt */ + + /* -eq */ + case TO_INTEQ: + /* -ne */ + case TO_INTNE: + /* -ge */ + case TO_INTGE: + /* -gt */ + case TO_INTGT: + /* -le */ + case TO_INTLE: + /* -lt */ + case TO_INTLT: if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) || !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) { /* error already printed.. */ te->flags |= TEF_ERROR; return (1); } - switch ((int)op) { + switch (op) { case TO_INTEQ: return (v1 == v2); case TO_INTNE: @@ -2918,49 +3029,47 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, return (v1 <= v2); case TO_INTLT: return (v1 < v2); + default: + /* NOTREACHED */ + break; } - case TO_FILNT: /* -nt */ - /* ksh88/ksh93 succeed if file2 can't be stated + /* NOTREACHED */ + + /* -nt */ + case TO_FILNT: + /* + * ksh88/ksh93 succeed if file2 can't be stated * (subtly different from 'does not exist'). */ return (stat(opnd1, &b1) == 0 && (((s = stat(opnd2, &b2)) == 0 && b1.st_mtime > b2.st_mtime) || s < 0)); - case TO_FILOT: /* -ot */ - /* ksh88/ksh93 succeed if file1 can't be stated + + /* -ot */ + case TO_FILOT: + /* + * ksh88/ksh93 succeed if file1 can't be stated * (subtly different from 'does not exist'). */ return (stat(opnd2, &b2) == 0 && (((s = stat(opnd1, &b1)) == 0 && b1.st_mtime < b2.st_mtime) || s < 0)); - case TO_FILEQ: /* -ef */ + + /* -ef */ + case TO_FILEQ: return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 && b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); + + /* all other cases */ + case TO_NONOP: + case TO_NONNULL: + /* throw the error */ + break; } (*te->error)(te, 0, "internal error: unknown op"); return (1); } -/* On most/all unixen, access() says everything is executable for root... */ -static int -test_eaccess(const char *pathl, int mode) -{ - int rv; - - if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) { - struct stat statb; - - if (stat(pathl, &statb) < 0) - rv = -1; - else if (S_ISDIR(statb.st_mode)) - rv = 0; - else - rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ? - 0 : -1; - } - return (rv); -} - int test_parse(Test_env *te) { @@ -3020,7 +3129,7 @@ test_primary(Test_env *te, bool do_eval) if (te->flags & TEF_ERROR) return (0); if (!(*te->isa)(te, TM_CPAREN)) { - (*te->error)(te, 0, "missing closing paren"); + (*te->error)(te, 0, "missing )"); return (0); } return (rv); @@ -3280,7 +3389,8 @@ c_ulimit(const char **wp) all = true; break; case '?': - bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]"); + bi_errorf("%s: %s", "usage", + "ulimit [-acdfHLlmnpSsTtvw] [value]"); return (1); default: what = optc; @@ -3336,7 +3446,7 @@ set_ulimit(const struct limits *l, const char *v, int how) } if (getrlimit(l->resource, &limit) < 0) { - /* some cannot be read, e.g. Linux RLIMIT_LOCKS */ + /* some can't be read, e.g. Linux RLIMIT_LOCKS */ limit.rlim_cur = RLIM_INFINITY; limit.rlim_max = RLIM_INFINITY; } @@ -3379,15 +3489,20 @@ c_rename(const char **wp) { int rv = 1; - if (wp == NULL /* argv */ || - wp[0] == NULL /* name of builtin */ || - wp[1] == NULL /* first argument */ || - wp[2] == NULL /* second argument */ || - wp[3] != NULL /* no further args please */) - bi_errorf(T_synerr); - else if ((rv = rename(wp[1], wp[2])) != 0) { + /* skip argv[0] */ + ++wp; + if (wp[0] && !strcmp(wp[0], "--")) + /* skip "--" (options separator) */ + ++wp; + + /* check for exactly two arguments */ + if (wp[0] == NULL /* first argument */ || + wp[1] == NULL /* second argument */ || + wp[2] != NULL /* no further args please */) + bi_errorf(Tsynerr); + else if ((rv = rename(wp[0], wp[1])) != 0) { rv = errno; - bi_errorf("failed: %s", strerror(rv)); + bi_errorf("%s: %s", "failed", strerror(rv)); } return (rv); @@ -3399,31 +3514,161 @@ c_realpath(const char **wp) int rv = 1; char *buf; - if (wp != NULL && wp[0] != NULL && wp[1] != NULL) { - if (strcmp(wp[1], "--")) { - if (wp[2] == NULL) { - wp += 1; - rv = 0; - } - } else { - if (wp[2] != NULL && wp[3] == NULL) { - wp += 2; - rv = 0; - } - } - } + /* skip argv[0] */ + ++wp; + if (wp[0] && !strcmp(wp[0], "--")) + /* skip "--" (options separator) */ + ++wp; - if (rv) - bi_errorf(T_synerr); - else if ((buf = do_realpath(*wp)) == NULL) { + /* check for exactly one argument */ + if (wp[0] == NULL || wp[1] != NULL) + bi_errorf(Tsynerr); + else if ((buf = do_realpath(wp[0])) == NULL) { rv = errno; - bi_errorf("%s: %s", *wp, strerror(rv)); + bi_errorf("%s: %s", wp[0], strerror(rv)); if ((unsigned int)rv > 255) rv = 255; } else { shprintf("%s\n", buf); afree(buf, ATEMP); + rv = 0; } return (rv); } + +int +c_cat(const char **wp) +{ + int fd = STDIN_FILENO, rv; + ssize_t n, w; + const char *fn = "<stdin>"; + char *buf, *cp; +#define MKSH_CAT_BUFSIZ 4096 + + if ((buf = malloc_osfunc(MKSH_CAT_BUFSIZ)) == NULL) { + bi_errorf(Toomem, (unsigned long)MKSH_CAT_BUFSIZ); + return (1); + } + + /* parse options: POSIX demands we support "-u" as no-op */ + while ((rv = ksh_getopt(wp, &builtin_opt, "u")) != -1) { + switch (rv) { + case 'u': + /* we already operate unbuffered */ + break; + default: + bi_errorf(Tsynerr); + return (1); + } + } + wp += builtin_opt.optind; + rv = 0; + + do { + if (*wp) { + fn = *wp++; + if (fn[0] == '-' && fn[1] == '\0') + fd = STDIN_FILENO; + else if ((fd = open(fn, O_RDONLY)) < 0) { + rv = errno; + bi_errorf("%s: %s", fn, strerror(rv)); + rv = 1; + continue; + } + } + while (/* CONSTCOND */ 1) { + n = blocking_read(fd, (cp = buf), MKSH_CAT_BUFSIZ); + if (n == -1) { + if (errno == EINTR) { + /* give the user a chance to ^C out */ + intrcheck(); + /* interrupted, try again */ + continue; + } + /* an error occured during reading */ + rv = errno; + bi_errorf("%s: %s", fn, strerror(rv)); + rv = 1; + break; + } else if (n == 0) + /* end of file reached */ + break; + while (n) { + w = write(STDOUT_FILENO, cp, n); + if (w == -1) { + if (errno == EINTR) + /* interrupted, try again */ + continue; + /* an error occured during writing */ + rv = errno; + bi_errorf("%s: %s", "<stdout>", + strerror(rv)); + rv = 1; + if (fd != STDIN_FILENO) + close(fd); + goto out; + } + n -= w; + cp += w; + } + } + if (fd != STDIN_FILENO) + close(fd); + } while (*wp); + + out: + free_osfunc(buf); + return (rv); +} + +#if HAVE_SELECT +int +c_sleep(const char **wp) +{ + struct timeval tv; + int rv = 1; + + /* skip argv[0] */ + ++wp; + if (wp[0] && !strcmp(wp[0], "--")) + /* skip "--" (options separator) */ + ++wp; + + if (!wp[0] || wp[1]) + bi_errorf(Tsynerr); + else if (parse_usec(wp[0], &tv)) + bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno), wp[0]); + else { +#ifndef MKSH_NOPROSPECTOFWORK + sigset_t omask; + + /* block SIGCHLD from interrupting us, though */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif + if (select(0, NULL, NULL, NULL, &tv) == 0 || errno == EINTR) + /* + * strictly speaking only for SIGALRM, but the + * execution may be interrupted by other signals + */ + rv = 0; + else + bi_errorf("%s: %s", Tselect, strerror(errno)); +#ifndef MKSH_NOPROSPECTOFWORK + sigprocmask(SIG_SETMASK, &omask, NULL); +#endif + } + return (rv); +} +#endif + +#if defined(ANDROID) +static int +c_android_lsmod(const char **wp MKSH_A_UNUSED) +{ + const char *cwp[3] = { "cat", "/proc/modules", NULL }; + + builtin_argv0 = cwp[0]; + return (c_cat(cwp)); +} +#endif diff --git a/src/histrap.c b/src/histrap.c index 2ac4c38..4a4a275 100644 --- a/src/histrap.c +++ b/src/histrap.c @@ -2,7 +2,7 @@ /* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,11 +22,11 @@ */ #include "sh.h" -#if HAVE_PERSISTENT_HISTORY +#if HAVE_SYS_FILE_H #include <sys/file.h> #endif -__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.98 2010/07/24 17:08:29 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.111 2011/09/07 15:24:16 tg Exp $"); /*- * MirOS: This is the default mapping type, and need not be specified. @@ -60,11 +60,15 @@ static int hstarted; /* set after hist_init() called */ static Source *hist_source; #if HAVE_PERSISTENT_HISTORY -static char *hname; /* current name of history file */ +/* current history file: name, fd, size */ +static char *hname; static int histfd; -static int hsize; +static size_t hsize; #endif +static const char Tnot_in_history[] = "not in history"; +#define Thistory (Tnot_in_history + 7) + int c_fc(const char **wp) { @@ -79,39 +83,50 @@ c_fc(const char **wp) char **hfirst, **hlast, **hp; if (!Flag(FTALKING_I)) { - bi_errorf("history functions not available"); + bi_errorf("history %ss not available", Tfunction); return (1); } while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) switch (optc) { + case 'e': p = builtin_opt.optarg; if (ksh_isdash(p)) sflag = true; else { size_t len = strlen(p); + + /* almost certainly not overflowing */ editor = alloc(len + 4, ATEMP); memcpy(editor, p, len); memcpy(editor + len, " $_", 4); } break; - case 'g': /* non-AT&T ksh */ + + /* non-AT&T ksh */ + case 'g': gflag = true; break; + case 'l': lflag = true; break; + case 'n': nflag = true; break; + case 'r': rflag = true; break; - case 's': /* POSIX version of -e - */ + + /* POSIX version of -e - */ + case 's': sflag = true; break; + /* kludge city - accept -num as -- -num (kind of) */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': @@ -126,6 +141,7 @@ c_fc(const char **wp) return (1); } break; + case '?': return (1); } @@ -183,11 +199,12 @@ c_fc(const char **wp) /* can't fail if hfirst didn't fail */ hlast = hist_get_newest(false); } else { - /* POSIX says not an error if first/last out of bounds - * when range is specified; AT&T ksh and pdksh allow out of - * bounds for -l as well. + /* + * POSIX says not an error if first/last out of bounds + * when range is specified; AT&T ksh and pdksh allow out + * of bounds for -l as well. */ - hfirst = hist_get(first, (lflag || last) ? true : false, lflag); + hfirst = hist_get(first, tobool(lflag || last), lflag); if (!hfirst) return (1); hlast = last ? hist_get(last, true, lflag) : @@ -199,7 +216,8 @@ c_fc(const char **wp) char **temp; temp = hfirst; hfirst = hlast; hlast = temp; - rflag = !rflag; /* POSIX */ + /* POSIX */ + rflag = !rflag; } /* List history */ @@ -230,15 +248,16 @@ c_fc(const char **wp) tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps); if (!(shf = tf->shf)) { - bi_errorf("cannot create temp file %s - %s", - tf->name, strerror(errno)); + bi_errorf("can't %s temporary file %s: %s", + "create", tf->name, strerror(errno)); return (1); } for (hp = rflag ? hlast : hfirst; hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) shf_fprintf(shf, "%s\n", *hp); if (shf_close(shf) == EOF) { - bi_errorf("error writing temporary file - %s", strerror(errno)); + bi_errorf("can't %s temporary file %s: %s", + "write", tf->name, strerror(errno)); return (1); } @@ -263,11 +282,19 @@ c_fc(const char **wp) int n; if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { - bi_errorf("cannot open temp file %s", tf->name); + bi_errorf("can't %s temporary file %s: %s", + "open", tf->name, strerror(errno)); return (1); } - n = stat(tf->name, &statb) < 0 ? 128 : statb.st_size + 1; + if (stat(tf->name, &statb) < 0) + n = 128; + else if (statb.st_size > (1024 * 1048576)) { + bi_errorf("%s %s too large: %lu", Thistory, + "file", (unsigned long)statb.st_size); + goto errout; + } else + n = statb.st_size + 1; Xinit(xs, xp, n, hist_source->areap); while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { xp += n; @@ -275,8 +302,9 @@ c_fc(const char **wp) XcheckN(xs, xp, Xlength(xs, xp)); } if (n < 0) { - bi_errorf("error reading temp file %s - %s", - tf->name, strerror(shf_errno(shf))); + bi_errorf("can't %s temporary file %s: %s", + "read", tf->name, strerror(shf_errno(shf))); + errout: shf_close(shf); return (1); } @@ -299,18 +327,22 @@ hist_execute(char *cmd) for (p = cmd; p; p = q) { if ((q = strchr(p, '\n'))) { - *q++ = '\0'; /* kill the newline */ - if (!*q) /* ignore trailing newline */ + /* kill the newline */ + *q++ = '\0'; + if (!*q) + /* ignore trailing newline */ q = NULL; } histsave(&hist_source->line, p, true, true); - shellf("%s\n", p); /* POSIX doesn't say this is done... */ - if (q) /* restore \n (trailing \n not restored) */ + /* POSIX doesn't say this is done... */ + shellf("%s\n", p); + if (q) + /* restore \n (trailing \n not restored) */ q[-1] = '\n'; } - /* + /*- * Commands are executed here instead of pushing them onto the * input 'cause POSIX says the redirection and variable assignments * in @@ -333,9 +365,9 @@ hist_replace(char **hp, const char *pat, const char *rep, bool globr) strdupx(line, *hp, ATEMP); else { char *s, *s1; - int pat_len = strlen(pat); - int rep_len = strlen(rep); - int len; + size_t pat_len = strlen(pat); + size_t rep_len = strlen(rep); + size_t len; XString xs; char *xp; bool any_subst = false; @@ -346,13 +378,15 @@ hist_replace(char **hp, const char *pat, const char *rep, bool globr) any_subst = true; len = s1 - s; XcheckN(xs, xp, len + rep_len); - memcpy(xp, s, len); /* first part */ + /*; first part */ + memcpy(xp, s, len); xp += len; - memcpy(xp, rep, rep_len); /* replacement */ + /* replacement */ + memcpy(xp, rep, rep_len); xp += rep_len; } if (!any_subst) { - bi_errorf("substitution failed"); + bi_errorf("bad substitution"); return (1); } len = strlen(s) + 1; @@ -380,18 +414,18 @@ hist_get(const char *str, bool approx, bool allow_cur) if (approx) hp = hist_get_oldest(); else { - bi_errorf("%s: not in history", str); + bi_errorf("%s: %s", str, Tnot_in_history); hp = NULL; } } else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) { if (approx) hp = hist_get_newest(allow_cur); else { - bi_errorf("%s: not in history", str); + bi_errorf("%s: %s", str, Tnot_in_history); hp = NULL; } } else if (!allow_cur && hp == histptr) { - bi_errorf("%s: invalid range", str); + bi_errorf("%s: %s", str, "invalid range"); hp = NULL; } } else { @@ -399,7 +433,7 @@ hist_get(const char *str, bool approx, bool allow_cur) /* the -1 is to avoid the current fc command */ if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0) - bi_errorf("%s: not in history", str); + bi_errorf("%s: %s", str, Tnot_in_history); else hp = &history[n]; } @@ -428,9 +462,9 @@ hist_get_oldest(void) return (history); } -/******************************/ -/* Back up over last histsave */ -/******************************/ +/* + * Back up over last histsave + */ static void histbackup(void) { @@ -475,10 +509,10 @@ histnum(int n) int findhist(int start, int fwd, const char *str, int anchored) { - char **hp; - int maxhist = histptr - history; - int incr = fwd ? 1 : -1; - int len = strlen(str); + char **hp; + int maxhist = histptr - history; + int incr = fwd ? 1 : -1; + size_t len = strlen(str); if (start < 0 || start >= maxhist) start = maxhist; @@ -492,26 +526,6 @@ findhist(int start, int fwd, const char *str, int anchored) return (-1); } -int -findhistrel(const char *str) -{ - int maxhist = histptr - history; - int start = maxhist - 1; - int rec; - - getn(str, &rec); - if (rec == 0) - return (-1); - if (rec > 0) { - if (rec > maxhist) - return (-1); - return (rec - 1); - } - if (rec > maxhist) - return (-1); - return (start + rec + 1); -} - /* * set history * this means reallocating the dataspace @@ -528,7 +542,7 @@ sethistsize(int n) cursize = n; } - history = aresize(history, n * sizeof(char *), APERM); + history = aresize2(history, n, sizeof(char *), APERM); histsize = n; histptr = history + cursize; @@ -579,7 +593,7 @@ init_histvec(void) { if (history == (char **)NULL) { histsize = HISTORYSIZE; - history = alloc(histsize * sizeof(char *), APERM); + history = alloc2(histsize, sizeof(char *), APERM); histptr = history - 1; } } @@ -645,7 +659,8 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) hp = histptr; - if (++hp >= history + histsize) { /* remove oldest command */ + if (++hp >= history + histsize) { + /* remove oldest command */ afree(*history, APERM); for (hp = history; hp < history + histsize - 1; hp++) hp[0] = hp[1]; @@ -665,7 +680,7 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) * if your system ain't got it - then you'll have to undef HISTORYFILE */ -/* +/*- * Open a history file * Format is: * Bytes 1, 2: @@ -685,6 +700,7 @@ hist_init(Source *s) #if HAVE_PERSISTENT_HISTORY unsigned char *base; int lines, fd, rv = 0; + off_t hfsize; #endif if (Flag(FTALKING) == 0) @@ -710,7 +726,10 @@ hist_init(Source *s) (void)flock(histfd, LOCK_EX); - hsize = lseek(histfd, (off_t)0, SEEK_END); + hfsize = lseek(histfd, (off_t)0, SEEK_END); + hsize = 1024 * 1048576; + if (hfsize < (off_t)hsize) + hsize = (size_t)hfsize; if (hsize == 0) { /* add magic */ @@ -746,8 +765,9 @@ hist_init(Source *s) hist_finish(); if (rv) { hiniterr: - bi_errorf("cannot unlink HISTFILE %s" - " - %s", hname, strerror(errno)); + bi_errorf("can't %s %s: %s", + "unlink HISTFILE", hname, + strerror(errno)); hsize = 0; return; } @@ -758,7 +778,10 @@ hist_init(Source *s) munmap((caddr_t)base, hsize); } (void)flock(histfd, LOCK_UN); - hsize = lseek(histfd, (off_t)0, SEEK_END); + hfsize = lseek(histfd, (off_t)0, SEEK_END); + hsize = 1024 * 1048576; + if (hfsize < (off_t)hsize) + hsize = hfsize; #endif } @@ -954,36 +977,34 @@ histinsert(Source *s, int lno, const char *line) static void writehistfile(int lno, char *cmd) { - int sizenow; - unsigned char *base; - unsigned char *news; - int bytes; - unsigned char hdr[5]; + off_t sizenow; + ssize_t bytes; + unsigned char *base, *news, hdr[5]; (void)flock(histfd, LOCK_EX); sizenow = lseek(histfd, (off_t)0, SEEK_END); - if (sizenow != hsize) { + if ((sizenow <= (1024 * 1048576)) && ((size_t)sizenow != hsize)) { /* * Things have changed */ - if (sizenow > hsize) { + if ((size_t)sizenow > hsize) { /* someone has added some lines */ - bytes = sizenow - hsize; - base = (void *)mmap(NULL, sizenow, PROT_READ, + bytes = (size_t)sizenow - hsize; + base = (void *)mmap(NULL, (size_t)sizenow, PROT_READ, MAP_FILE | MAP_PRIVATE, histfd, (off_t)0); if (base == (unsigned char *)MAP_FAILED) goto bad; news = base + hsize; if (*news != COMMAND) { - munmap((caddr_t)base, sizenow); + munmap((caddr_t)base, (size_t)sizenow); goto bad; } hist_source->line--; histload(hist_source, news, bytes); hist_source->line++; lno = hist_source->line; - munmap((caddr_t)base, sizenow); - hsize = sizenow; + munmap((caddr_t)base, (size_t)sizenow); + hsize = (size_t)sizenow; } else { /* it has shrunk */ /* but to what? */ @@ -1004,7 +1025,10 @@ writehistfile(int lno, char *cmd) if ((write(histfd, hdr, 5) != 5) || (write(histfd, cmd, bytes) != bytes)) goto bad; - hsize = lseek(histfd, (off_t)0, SEEK_END); + sizenow = lseek(histfd, (off_t)0, SEEK_END); + hsize = 1024 * 1048576; + if (sizenow < (off_t)hsize) + hsize = (size_t)sizenow; } (void)flock(histfd, LOCK_UN); return; @@ -1048,10 +1072,12 @@ inittraps(void) int i; const char *cs; + trap_exstat = -1; + /* Populate sigtraps based on sys_signame and sys_siglist. */ for (i = 0; i <= NSIG; i++) { sigtraps[i].signal = i; - if (i == SIGERR_) { + if (i == ksh_SIGERR) { sigtraps[i].name = "ERR"; sigtraps[i].mess = "Error handler"; } else { @@ -1085,10 +1111,12 @@ inittraps(void) #endif if ((sigtraps[i].mess == NULL) || (sigtraps[i].mess[0] == '\0')) - sigtraps[i].mess = shf_smprintf("Signal %d", i); + sigtraps[i].mess = shf_smprintf("%s %d", + "Signal", i); } } - sigtraps[SIGEXIT_].name = "EXIT"; /* our name for signal 0 */ + /* our name for signal 0 */ + sigtraps[ksh_SIGEXIT].name = "EXIT"; (void)sigemptyset(&Sigact_ign.sa_mask); Sigact_ign.sa_flags = 0; /* interruptible */ @@ -1096,7 +1124,8 @@ inittraps(void) sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR; sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR; - sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */ + /* SIGTERM is not fatal for interactive */ + sigtraps[SIGTERM].flags |= TF_DFL_INTR; sigtraps[SIGHUP].flags |= TF_FATAL; sigtraps[SIGCHLD].flags |= TF_SHELL_USES; @@ -1165,7 +1194,7 @@ void trapsig(int i) { Trap *p = &sigtraps[i]; - int errno_ = errno; + int errno_sv = errno; trap = p->set = 1; if (p->flags & TF_DFL_INTR) @@ -1176,7 +1205,7 @@ trapsig(int i) } if (p->shtrap) (*p->shtrap)(i); - errno = errno_; + errno = errno_sv; } /* @@ -1252,22 +1281,29 @@ runtraps(int flag) intrsig = 0; if (flag & TF_FATAL) fatal_trap = 0; + ++trap_nested; for (p = sigtraps, i = NSIG+1; --i >= 0; p++) if (p->set && (!flag || ((p->flags & flag) && p->trap == NULL))) - runtrap(p); + runtrap(p, false); + if (!--trap_nested) + runtrap(NULL, true); } void -runtrap(Trap *p) +runtrap(Trap *p, bool is_last) { - int i = p->signal; - char *trapstr = p->trap; - int oexstat; - int old_changed = 0; - + int old_changed = 0, i; + char *trapstr; + + if (p == NULL) + /* just clean up, see runtraps() above */ + goto donetrap; + i = p->signal; + trapstr = p->trap; p->set = 0; - if (trapstr == NULL) { /* SIG_DFL */ + if (trapstr == NULL) { + /* SIG_DFL */ if (p->flags & TF_FATAL) { /* eg, SIGHUP */ exstat = 128 + i; @@ -1278,23 +1314,25 @@ runtrap(Trap *p) exstat = 128 + i; unwind(LINTR); } - return; + goto donetrap; } - if (trapstr[0] == '\0') /* SIG_IGN */ - return; - if (i == SIGEXIT_ || i == SIGERR_) { /* avoid recursion on these */ + if (trapstr[0] == '\0') + /* SIG_IGN */ + goto donetrap; + if (i == ksh_SIGEXIT || i == ksh_SIGERR) { + /* avoid recursion on these */ old_changed = p->flags & TF_CHANGED; p->flags &= ~TF_CHANGED; p->trap = NULL; } - oexstat = exstat; + if (trap_exstat == -1) + trap_exstat = exstat; /* * Note: trapstr is fully parsed before anything is executed, thus * no problem with afree(p->trap) in settrap() while still in use. */ command(trapstr, current_lineno); - exstat = oexstat; - if (i == SIGEXIT_ || i == SIGERR_) { + if (i == ksh_SIGEXIT || i == ksh_SIGERR) { if (p->flags & TF_CHANGED) /* don't clear TF_CHANGED */ afree(trapstr, APERM); @@ -1302,6 +1340,13 @@ runtrap(Trap *p) p->trap = trapstr; p->flags |= old_changed; } + + donetrap: + /* we're the last trap of a sequence executed */ + if (is_last && trap_exstat != -1) { + exstat = trap_exstat; + trap_exstat = -1; + } } /* clear pending traps and reset user's trap handlers; used after fork(2) */ @@ -1341,7 +1386,8 @@ settrap(Trap *p, const char *s) if (p->trap) afree(p->trap, APERM); - strdupx(p->trap, s, APERM); /* handles s == 0 */ + /* handles s == NULL */ + strdupx(p->trap, s, APERM); p->flags |= TF_CHANGED; f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN; @@ -1385,7 +1431,8 @@ block_pipe(void) restore_dfl = 1; } else if (p->cursig == SIG_DFL) { setsig(p, SIG_IGN, SS_RESTORE_CURR); - restore_dfl = 1; /* restore to SIG_DFL */ + /* restore to SIG_DFL */ + restore_dfl = 1; } return (restore_dfl); } @@ -1407,7 +1454,7 @@ setsig(Trap *p, sig_t f, int flags) { struct sigaction sigact; - if (p->signal == SIGEXIT_ || p->signal == SIGERR_) + if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR) return (1); /* @@ -1448,7 +1495,8 @@ setsig(Trap *p, sig_t f, int flags) if (p->cursig != f) { p->cursig = f; (void)sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0 /* interruptible */; + /* interruptible */ + sigact.sa_flags = 0; sigact.sa_handler = f; sigaction(p->signal, &sigact, NULL); } @@ -1468,7 +1516,8 @@ setexecsig(Trap *p, int restore) /* restore original value for exec'd kids */ p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL); switch (restore & SS_RESTORE_MASK) { - case SS_RESTORE_CURR: /* leave things as they currently are */ + case SS_RESTORE_CURR: + /* leave things as they currently are */ break; case SS_RESTORE_ORIG: p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL; @@ -1,7 +1,7 @@ /* $OpenBSD: jobs.c,v 1.38 2009/12/12 04:28:44 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.69 2010/07/04 17:33:54 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.81 2011/08/27 18:06:46 tg Exp $"); #if HAVE_KILLPG #define mksh_killpg killpg @@ -63,7 +63,7 @@ struct proc { #define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */ #define JF_XXCOM 0x008 /* set for $(command) jobs */ #define JF_FG 0x010 /* running in foreground (also has tty pgrp) */ -#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */ +#define JF_SAVEDTTY 0x020 /* j->ttystat is valid */ #define JF_CHANGED 0x040 /* process has changed state */ #define JF_KNOWN 0x080 /* $! referenced */ #define JF_ZOMBIE 0x100 /* known, unwaited process */ @@ -87,7 +87,7 @@ struct job { int32_t age; /* number of jobs started */ Coproc_id coproc_id; /* 0 or id of coprocess output pipe */ #ifndef MKSH_UNEMPLOYED - struct termios ttystate;/* saved tty state for stopped jobs */ + struct termios ttystat; /* saved tty state for stopped jobs */ pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ #endif }; @@ -97,6 +97,7 @@ struct job { #define JW_INTERRUPT 0x01 /* ^C will stop the wait */ #define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */ #define JW_STOPPEDWAIT 0x04 /* wait even if job stopped */ +#define JW_PIPEST 0x08 /* want PIPESTATUS */ /* Error codes for j_lookup() */ #define JL_OK 0 @@ -124,8 +125,10 @@ static int32_t njobs; /* # of jobs started */ #define CHILD_MAX 25 #endif +#ifndef MKSH_NOPROSPECTOFWORK /* held_sigchld is set if sigchld occurs before a job is completely started */ static volatile sig_atomic_t held_sigchld; +#endif #ifndef MKSH_UNEMPLOYED static struct shf *shl_j; @@ -157,6 +160,7 @@ j_init(void) Flag(FMONITOR) = 0; #endif +#ifndef MKSH_NOPROSPECTOFWORK (void)sigemptyset(&sm_default); sigprocmask(SIG_SETMASK, &sm_default, NULL); @@ -165,6 +169,10 @@ j_init(void) setsig(&sigtraps[SIGCHLD], j_sigchld, SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); +#else + /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */ + setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); +#endif #ifndef MKSH_UNEMPLOYED if (!mflagset && Flag(FTALKING)) @@ -200,13 +208,26 @@ j_init(void) tty_init(true, true); } +static int +proc_errorlevel(Proc *p) +{ + switch (p->state) { + case PEXITED: + return (WEXITSTATUS(p->status)); + case PSIGNALLED: + return (128 + WTERMSIG(p->status)); + default: + return (0); + } +} + /* job cleanup before shell exit */ void j_exit(void) { /* kill stopped, and possibly running, jobs */ - Job *j; - int killed = 0; + Job *j; + bool killed = false; for (j = job_list; j != NULL; j = j->next) { if (j->ppid == procpid && @@ -214,7 +235,7 @@ j_exit(void) (j->state == PRUNNING && ((j->flags & JF_FG) || (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) { - killed = 1; + killed = true; if (j->pgrp == 0) kill_job(j, SIGHUP); else @@ -272,12 +293,12 @@ j_change(void) setsig(&sigtraps[SIGTTIN], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); /* wait to be given tty (POSIX.1, B.2, job control) */ - while (1) { + while (/* CONSTCOND */ 1) { pid_t ttypgrp; if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { - warningf(false, - "j_init: tcgetpgrp() failed: %s", + warningf(false, "%s: %s %s: %s", + "j_init", "tcgetpgrp", "failed", strerror(errno)); ttypgrp_ok = false; break; @@ -292,14 +313,13 @@ j_change(void) SS_RESTORE_DFL|SS_FORCE); if (ttypgrp_ok && kshpgrp != kshpid) { if (setpgid(0, kshpid) < 0) { - warningf(false, - "j_init: setpgid() failed: %s", - strerror(errno)); + warningf(false, "%s: %s %s: %s", "j_init", + "setpgid", "failed", strerror(errno)); ttypgrp_ok = false; } else { if (tcsetpgrp(tty_fd, kshpid) < 0) { - warningf(false, - "j_init: tcsetpgrp() failed: %s", + warningf(false, "%s: %s %s: %s", + "j_init", "tcsetpgrp", "failed", strerror(errno)); ttypgrp_ok = false; } else @@ -308,7 +328,8 @@ j_change(void) } } if (use_tty && !ttypgrp_ok) - warningf(false, "warning: won't have full job control"); + warningf(false, "%s: %s", "warning", + "won't have full job control"); if (tty_fd >= 0) tcgetattr(tty_fd, &tty_state); } else { @@ -332,21 +353,46 @@ j_change(void) } #endif +#if HAVE_NICE +/* run nice(3) and ignore the result */ +static void +ksh_nice(int ness) +{ +#if defined(__USE_FORTIFY_LEVEL) && (__USE_FORTIFY_LEVEL > 0) + int e; + + errno = 0; + /* this is gonna annoy users; complain to your distro, people! */ + if (nice(ness) == -1 && (e = errno) != 0) + warningf(false, "%s: %s", "bgnice", strerror(e)); +#else + (void)nice(ness); +#endif +} +#endif + /* execute tree in child subprocess */ int exchild(struct op *t, int flags, volatile int *xerrok, - /* used if XPCLOSE or XCCLOSE */ int close_fd) + /* used if XPCLOSE or XCCLOSE */ + int close_fd) { - static Proc *last_proc; /* for pipelines */ + /* for pipelines */ + static Proc *last_proc; - int rv = 0, forksleep; + int rv = 0, forksleep, jwflags = JW_NONE; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; - struct { - Proc *p; - Job *j; - pid_t cldpid; - } pi; +#endif + Proc *p; + Job *j; + pid_t cldpid; + + if (flags & XPIPEST) { + flags &= ~XPIPEST; + jwflags |= JW_PIPEST; + } if (flags & XEXEC) /* @@ -355,103 +401,111 @@ exchild(struct op *t, int flags, */ return (execute(t, flags & (XEXEC | XERROK), xerrok)); +#ifndef MKSH_NOPROSPECTOFWORK /* no SIGCHLDs while messing with job and process lists */ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif - pi.p = new_proc(); - pi.p->next = NULL; - pi.p->state = PRUNNING; - pi.p->status = 0; - pi.p->pid = 0; + p = new_proc(); + p->next = NULL; + p->state = PRUNNING; + p->status = 0; + p->pid = 0; /* link process into jobs list */ if (flags & XPIPEI) { /* continuing with a pipe */ if (!last_job) - internal_errorf( - "exchild: XPIPEI and no last_job - pid %d", + internal_errorf("%s %d", + "exchild: XPIPEI and no last_job - pid", (int)procpid); - pi.j = last_job; + j = last_job; if (last_proc) - last_proc->next = pi.p; - last_proc = pi.p; + last_proc->next = p; + last_proc = p; } else { - pi.j = new_job(); /* fills in pi.j->job */ + /* fills in j->job */ + j = new_job(); /* * we don't consider XXCOMs foreground since they don't get * tty process group and we don't save or restore tty modes. */ - pi.j->flags = (flags & XXCOM) ? JF_XXCOM : + j->flags = (flags & XXCOM) ? JF_XXCOM : ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE)); - timerclear(&pi.j->usrtime); - timerclear(&pi.j->systime); - pi.j->state = PRUNNING; - pi.j->pgrp = 0; - pi.j->ppid = procpid; - pi.j->age = ++njobs; - pi.j->proc_list = pi.p; - pi.j->coproc_id = 0; - last_job = pi.j; - last_proc = pi.p; - put_job(pi.j, PJ_PAST_STOPPED); + timerclear(&j->usrtime); + timerclear(&j->systime); + j->state = PRUNNING; + j->pgrp = 0; + j->ppid = procpid; + j->age = ++njobs; + j->proc_list = p; + j->coproc_id = 0; + last_job = j; + last_proc = p; + put_job(j, PJ_PAST_STOPPED); } - snptreef(pi.p->command, sizeof(pi.p->command), "%T", t); + vistree(p->command, sizeof(p->command), t); /* create child process */ forksleep = 1; - while ((pi.cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) { - if (intrsig) /* allow user to ^C out... */ + while ((cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) { + if (intrsig) + /* allow user to ^C out... */ break; sleep(forksleep); forksleep <<= 1; } - if (pi.cldpid < 0) { - kill_job(pi.j, SIGKILL); - remove_job(pi.j, "fork failed"); + /* ensure $RANDOM changes between parent and child */ + rndset((long)cldpid); + /* fork failed? */ + if (cldpid < 0) { + kill_job(j, SIGKILL); + remove_job(j, "fork failed"); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); - errorf("cannot fork - try again"); +#endif + errorf("can't fork - try again"); } - pi.p->pid = pi.cldpid ? pi.cldpid : (procpid = getpid()); - - /* - * ensure next child gets a (slightly) different $RANDOM sequence - * from its parent process and other child processes - */ - change_random(&pi, sizeof(pi)); + p->pid = cldpid ? cldpid : (procpid = getpid()); #ifndef MKSH_UNEMPLOYED /* job control set up */ if (Flag(FMONITOR) && !(flags&XXCOM)) { - int dotty = 0; - if (pi.j->pgrp == 0) { /* First process */ - pi.j->pgrp = pi.p->pid; - dotty = 1; + bool dotty = false; + if (j->pgrp == 0) { + /* First process */ + j->pgrp = p->pid; + dotty = true; } - /* set pgrp in both parent and child to deal with race + /* + * set pgrp in both parent and child to deal with race * condition */ - setpgid(pi.p->pid, pi.j->pgrp); + setpgid(p->pid, j->pgrp); if (ttypgrp_ok && dotty && !(flags & XBGND)) - tcsetpgrp(tty_fd, pi.j->pgrp); + tcsetpgrp(tty_fd, j->pgrp); } #endif /* used to close pipe input fd */ - if (close_fd >= 0 && (((flags & XPCLOSE) && pi.cldpid) || - ((flags & XCCLOSE) && !pi.cldpid))) + if (close_fd >= 0 && (((flags & XPCLOSE) && cldpid) || + ((flags & XCCLOSE) && !cldpid))) close(close_fd); - if (!pi.cldpid) { + if (!cldpid) { /* child */ /* Do this before restoring signal */ if (flags & XCOPROC) coproc_cleanup(false); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif cleanup_parents_env(); #ifndef MKSH_UNEMPLOYED - /* If FMONITOR or FTALKING is set, these signals are ignored, + /* + * If FMONITOR or FTALKING is set, these signals are ignored, * if neither FMONITOR nor FTALKING are set, the signals have * their inherited values. */ @@ -463,7 +517,7 @@ exchild(struct op *t, int flags, #endif #if HAVE_NICE if (Flag(FBGNICE) && (flags & XBGND)) - (void)nice(4); + ksh_nice(4); #endif if ((flags & XBGND) #ifndef MKSH_UNEMPLOYED @@ -480,7 +534,8 @@ exchild(struct op *t, int flags, close(forksleep); } } - remove_job(pi.j, "child"); /* in case of $(jobs) command */ + /* in case of $(jobs) command */ + remove_job(j, "child"); nzombie = 0; #ifndef MKSH_UNEMPLOYED ttypgrp_ok = false; @@ -494,8 +549,9 @@ exchild(struct op *t, int flags, #ifndef MKSH_SMALL if (t->type == TPIPE) unwind(LLEAVE); - internal_warningf("exchild: execute() returned"); - fptreef(shl_out, 2, "exchild: tried to execute {\n%T\n}\n", t); + internal_warningf("%s: %s", "exchild", "execute() returned"); + fptreef(shl_out, 8, "%s: tried to execute {\n\t%T\n}\n", + "exchild", t); shf_flush(shl_out); #endif unwind(LLEAVE); @@ -503,31 +559,33 @@ exchild(struct op *t, int flags, } /* shell (parent) stuff */ - if (!(flags & XPIPEO)) { /* last process in a job */ - j_startjob(pi.j); + if (!(flags & XPIPEO)) { + /* last process in a job */ + j_startjob(j); if (flags & XCOPROC) { - pi.j->coproc_id = coproc.id; + j->coproc_id = coproc.id; /* n jobs using co-process output */ coproc.njobs++; /* j using co-process input */ - coproc.job = (void *)pi.j; + coproc.job = (void *)j; } if (flags & XBGND) { - j_set_async(pi.j); + j_set_async(j); if (Flag(FTALKING)) { - shf_fprintf(shl_out, "[%d]", pi.j->job); - for (pi.p = pi.j->proc_list; pi.p; - pi.p = pi.p->next) + shf_fprintf(shl_out, "[%d]", j->job); + for (p = j->proc_list; p; p = p->next) shf_fprintf(shl_out, " %d", - (int)pi.p->pid); + (int)p->pid); shf_putchar('\n', shl_out); shf_flush(shl_out); } } else - rv = j_waitj(pi.j, JW_NONE, "jw:last proc"); + rv = j_waitj(j, jwflags, "jw:last proc"); } +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (rv); } @@ -536,41 +594,53 @@ exchild(struct op *t, int flags, void startlast(void) { +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif - if (last_job) { /* no need to report error - waitlast() will do it */ + /* no need to report error - waitlast() will do it */ + if (last_job) { /* ensure it isn't removed by check_job() */ last_job->flags |= JF_WAITING; j_startjob(last_job); } +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif } /* wait for last job: only used for $(command) jobs */ int waitlast(void) { - int rv; - Job *j; + int rv; + Job *j; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif j = last_job; if (!j || !(j->flags & JF_STARTED)) { if (!j) - warningf(true, "waitlast: no last job"); + warningf(true, "%s: %s", "waitlast", "no last job"); else - internal_warningf("waitlast: not started"); + internal_warningf("%s: %s", "waitlast", "not started"); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); - return (125); /* not so arbitrary, non-zero value */ +#endif + /* not so arbitrary, non-zero value */ + return (125); } - rv = j_waitj(j, JW_NONE, "jw:waitlast"); + rv = j_waitj(j, JW_NONE, "waitlast"); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (rv); } @@ -579,13 +649,13 @@ waitlast(void) int waitfor(const char *cp, int *sigp) { - int rv; - Job *j; - int ecode; - int flags = JW_INTERRUPT|JW_ASYNCNOTIFY; + int rv, ecode, flags = JW_INTERRUPT|JW_ASYNCNOTIFY; + Job *j; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif *sigp = 0; @@ -599,18 +669,24 @@ waitfor(const char *cp, int *sigp) if (j->ppid == procpid && j->state == PRUNNING) break; if (!j) { +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (-1); } } else if ((j = j_lookup(cp, &ecode))) { /* don't report normal job completion */ flags &= ~JW_ASYNCNOTIFY; if (j->ppid != procpid) { +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (-1); } } else { +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif if (ecode != JL_NOSUCH) bi_errorf("%s: %s", cp, lookup_msgs[ecode]); return (-1); @@ -619,9 +695,12 @@ waitfor(const char *cp, int *sigp) /* AT&T ksh will wait for stopped jobs - we don't */ rv = j_waitj(j, flags, "jw:waitfor"); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif - if (rv < 0) /* we were interrupted */ + if (rv < 0) + /* we were interrupted */ *sigp = 128 + -rv; return (rv); @@ -631,20 +710,24 @@ waitfor(const char *cp, int *sigp) int j_kill(const char *cp, int sig) { - Job *j; - int rv = 0; - int ecode; + Job *j; + int rv = 0, ecode; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif if ((j = j_lookup(cp, &ecode)) == NULL) { +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif bi_errorf("%s: %s", cp, lookup_msgs[ecode]); return (1); } - if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */ + if (j->pgrp == 0) { + /* started when !Flag(FMONITOR) */ if (kill_job(j, sig) < 0) { bi_errorf("%s: %s", cp, strerror(errno)); rv = 1; @@ -660,7 +743,9 @@ j_kill(const char *cp, int sig) } } +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (rv); } @@ -670,11 +755,10 @@ j_kill(const char *cp, int sig) int j_resume(const char *cp, int bg) { - Job *j; - Proc *p; - int ecode; - int running; - int rv = 0; + Job *j; + Proc *p; + int ecode, rv = 0; + bool running; sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); @@ -694,12 +778,12 @@ j_resume(const char *cp, int bg) if (bg) shprintf("[%d] ", j->job); - running = 0; + running = false; for (p = j->proc_list; p != NULL; p = p->next) { if (p->state == PSTOPPED) { p->state = PRUNNING; p->status = 0; - running = 1; + running = true; } shf_puts(p->command, shl_stdout); if (p->next) @@ -717,7 +801,7 @@ j_resume(const char *cp, int bg) /* attach tty to job */ if (j->state == PRUNNING) { if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) - tcsetattr(tty_fd, TCSADRAIN, &j->ttystate); + tcsetattr(tty_fd, TCSADRAIN, &j->ttystat); /* See comment in j_waitj regarding saved_ttypgrp. */ if (ttypgrp_ok && tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ? @@ -725,12 +809,11 @@ j_resume(const char *cp, int bg) rv = errno; if (j->flags & JF_SAVEDTTY) tcsetattr(tty_fd, TCSADRAIN, &tty_state); - sigprocmask(SIG_SETMASK, &omask, - NULL); - bi_errorf("1st tcsetpgrp(%d, %d) failed: %s", - tty_fd, - (int)((j->flags & JF_SAVEDTTYPGRP) ? - j->saved_ttypgrp : j->pgrp), + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s %s(%d, %ld) %s: %s", + "1st", "tcsetpgrp", tty_fd, + (long)((j->flags & JF_SAVEDTTYPGRP) ? + j->saved_ttypgrp : j->pgrp), "failed", strerror(rv)); return (1); } @@ -749,12 +832,12 @@ j_resume(const char *cp, int bg) if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) tcsetattr(tty_fd, TCSADRAIN, &tty_state); if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0) - warningf(true, - "fg: 2nd tcsetpgrp(%d, %ld) failed: %s", - tty_fd, (long)kshpgrp, strerror(errno)); + warningf(true, "%s %s(%d, %ld) %s: %s", + "fg: 2nd", "tcsetpgrp", tty_fd, + (long)kshpgrp, "failed", strerror(errno)); } sigprocmask(SIG_SETMASK, &omask, NULL); - bi_errorf("cannot continue job %s: %s", + bi_errorf("%s %s %s", "can't continue job", cp, strerror(err)); return (1); } @@ -773,8 +856,8 @@ j_resume(const char *cp, int bg) int j_stopped_running(void) { - Job *j; - int which = 0; + Job *j; + int which = 0; for (j = job_list; j != NULL; j = j->next) { #ifndef MKSH_UNEMPLOYED @@ -796,35 +879,23 @@ j_stopped_running(void) return (0); } -int -j_njobs(void) -{ - Job *j; - int nj = 0; - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); - for (j = job_list; j; j = j->next) - nj++; - - sigprocmask(SIG_SETMASK, &omask, NULL); - return (nj); -} - /* list jobs for jobs built-in */ int j_jobs(const char *cp, int slp, - int nflag) /* 0: short, 1: long, 2: pgrp */ + /* 0: short, 1: long, 2: pgrp */ + int nflag) { - Job *j, *tmp; - int how; - int zflag = 0; + Job *j, *tmp; + int how, zflag = 0; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif - if (nflag < 0) { /* kludge: print zombies */ + if (nflag < 0) { + /* kludge: print zombies */ nflag = 0; zflag = 1; } @@ -832,7 +903,9 @@ j_jobs(const char *cp, int slp, int ecode; if ((j = j_lookup(cp, &ecode)) == NULL) { +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif bi_errorf("%s: %s", cp, lookup_msgs[ecode]); return (1); } @@ -855,7 +928,9 @@ j_jobs(const char *cp, int slp, if (j->flags & JF_REMOVE) remove_job(j, "jobs"); } +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (0); } @@ -863,16 +938,19 @@ j_jobs(const char *cp, int slp, void j_notify(void) { - Job *j, *tmp; + Job *j, *tmp; +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif for (j = job_list; j; j = j->next) { #ifndef MKSH_UNEMPLOYED if (Flag(FMONITOR) && (j->flags & JF_CHANGED)) j_print(j, JP_MEDIUM, shl_out); #endif - /* Remove job after doing reports so there aren't + /* + * Remove job after doing reports so there aren't * multiple +/- jobs. */ if (j->state == PEXITED || j->state == PSIGNALLED) @@ -884,21 +962,27 @@ j_notify(void) remove_job(j, "notify"); } shf_flush(shl_out); +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif } /* Return pid of last process in last asynchronous job */ pid_t j_async(void) { +#ifndef MKSH_NOPROSPECTOFWORK sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif if (async_job) async_job->flags |= JF_KNOWN; +#ifndef MKSH_NOPROSPECTOFWORK sigprocmask(SIG_SETMASK, &omask, NULL); +#endif return (async_pid); } @@ -916,7 +1000,7 @@ j_set_async(Job *j) if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE) remove_job(async_job, "async"); if (!(j->flags & JF_STARTED)) { - internal_warningf("j_async: job not started"); + internal_warningf("%s: %s", "j_async", "job not started"); return; } async_job = j; @@ -930,8 +1014,8 @@ j_set_async(Job *j) if (!oldest) { /* XXX debugging */ if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) { - internal_warningf("j_async: bad nzombie (%d)", - nzombie); + internal_warningf("%s: bad nzombie (%d)", + "j_async", nzombie); nzombie = 0; } break; @@ -955,11 +1039,13 @@ j_startjob(Job *j) ; j->last_proc = p; +#ifndef MKSH_NOPROSPECTOFWORK if (held_sigchld) { held_sigchld = 0; /* Don't call j_sigchld() as it may remove job... */ kill(procpid, SIGCHLD); } +#endif } /* @@ -969,10 +1055,11 @@ j_startjob(Job *j) */ static int j_waitj(Job *j, - int flags, /* see JW_* */ + /* see JW_* */ + int flags, const char *where) { - int rv; + int rv; /* * No auto-notify on the job we are waiting on. @@ -988,12 +1075,17 @@ j_waitj(Job *j, while (j->state == PRUNNING || ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) { +#ifndef MKSH_NOPROSPECTOFWORK sigsuspend(&sm_default); +#else + j_sigchld(SIGCHLD); +#endif if (fatal_trap) { int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); runtraps(TF_FATAL); - j->flags |= oldf; /* not reached... */ + /* not reached... */ + j->flags |= oldf; } if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); @@ -1021,12 +1113,12 @@ j_waitj(Job *j, (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0) j->flags |= JF_SAVEDTTYPGRP; if (tcsetpgrp(tty_fd, kshpgrp) < 0) - warningf(true, - "j_waitj: tcsetpgrp(%d, %ld) failed: %s", - tty_fd, (long)kshpgrp, strerror(errno)); + warningf(true, "%s %s(%d, %ld) %s: %s", + "j_waitj:", "tcsetpgrp", tty_fd, + (long)kshpgrp, "failed", strerror(errno)); if (j->state == PSTOPPED) { j->flags |= JF_SAVEDTTY; - tcgetattr(tty_fd, &j->ttystate); + tcgetattr(tty_fd, &j->ttystat); } } #endif @@ -1084,6 +1176,38 @@ j_waitj(Job *j, j_systime = j->systime; rv = j->status; + if ((flags & JW_PIPEST) && (j->proc_list != NULL)) { + uint32_t num = 0; + Proc *p = j->proc_list; + struct tbl *vp; + + unset(vp_pipest, 1); + vp = vp_pipest; + vp->flag = DEFINED | ISSET | INTEGER | RDONLY | ARRAY | INT_U; + goto got_array; + + while (p != NULL) { + { + struct tbl *vq; + + /* strlen(vp_pipest->name) == 10 */ + vq = alloc(offsetof(struct tbl, name[0]) + 11, + vp_pipest->areap); + memset(vq, 0, offsetof(struct tbl, name[0])); + memcpy(vq->name, vp_pipest->name, 11); + vp->u.array = vq; + vp = vq; + } + vp->areap = vp_pipest->areap; + vp->ua.index = ++num; + vp->flag = DEFINED | ISSET | INTEGER | RDONLY | + ARRAY | INT_U | AINDEX; + got_array: + vp->val.i = proc_errorlevel(p); + p = p->next; + } + } + if (!(flags & JW_ASYNCNOTIFY) #ifndef MKSH_UNEMPLOYED && (!Flag(FMONITOR) || j->state != PSTOPPED) @@ -1119,6 +1243,7 @@ j_sigchld(int sig MKSH_A_UNUSED) int status; struct rusage ru0, ru1; +#ifndef MKSH_NOPROSPECTOFWORK /* * Don't wait for any processes if a job is partially started. * This is so we don't do away with the process group leader @@ -1130,10 +1255,15 @@ j_sigchld(int sig MKSH_A_UNUSED) held_sigchld = 1; return; } +#endif getrusage(RUSAGE_CHILDREN, &ru0); do { +#ifndef MKSH_NOPROSPECTOFWORK pid = waitpid(-1, &status, (WNOHANG|WUNTRACED)); +#else + pid = wait(&status); +#endif /* * return if this would block (0) or no children @@ -1175,8 +1305,14 @@ j_sigchld(int sig MKSH_A_UNUSED) else p->state = PEXITED; - check_job(j); /* check to see if entire job is done */ - } while (1); + /* check to see if entire job is done */ + check_job(j); + } +#ifndef MKSH_NOPROSPECTOFWORK + while (/* CONSTCOND */ 1); +#else + while (/* CONSTCOND */ 0); +#endif } /* @@ -1203,23 +1339,13 @@ check_job(Job *j) jstate = PRUNNING; for (p=j->proc_list; p != NULL; p = p->next) { if (p->state == PRUNNING) - return; /* some processes still running */ + /* some processes still running */ + return; if (p->state > jstate) jstate = p->state; } j->state = jstate; - - switch (j->last_proc->state) { - case PEXITED: - j->status = WEXITSTATUS(j->last_proc->status); - break; - case PSIGNALLED: - j->status = 128 + WTERMSIG(j->last_proc->status); - break; - default: - j->status = 0; - break; - } + j->status = proc_errorlevel(j->last_proc); /* * Note when co-process dies: can't be done in j_wait() nor @@ -1371,7 +1497,7 @@ j_print(Job *j, int how, struct shf *shf) if (p == j->proc_list) shf_fprintf(shf, "[%d] %c ", j->job, jobchar); else - shf_fprintf(shf, "%s", filler); + shf_puts(filler, shf); } if (how == JP_LONG) @@ -1416,9 +1542,10 @@ j_print(Job *j, int how, struct shf *shf) static Job * j_lookup(const char *cp, int *ecodep) { - Job *j, *last_match; - Proc *p; - int len, job = 0; + Job *j, *last_match; + Proc *p; + size_t len; + int job = 0; if (ksh_isdigit(*cp)) { getn(cp, &job); @@ -1463,7 +1590,8 @@ j_lookup(const char *cp, int *ecodep) return (j); break; - case '?': /* %?string */ + /* %?string */ + case '?': last_match = NULL; for (j = job_list; j != NULL; j = j->next) for (p = j->proc_list; p != NULL; p = p->next) @@ -1479,7 +1607,8 @@ j_lookup(const char *cp, int *ecodep) return (last_match); break; - default: /* %string */ + /* %string */ + default: len = strlen(cp); last_match = NULL; for (j = job_list; j != NULL; j = j->next) @@ -1568,7 +1697,7 @@ remove_job(Job *j, const char *where) for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev) ; if (curr != j) { - internal_warningf("remove_job: job not found (%s)", where); + internal_warningf("remove_job: job %s (%s)", "not found", where); return; } *prev = curr->next; diff --git a/src/lalloc.c b/src/lalloc.c index 79627d1..daaee57 100644 --- a/src/lalloc.c +++ b/src/lalloc.c @@ -1,32 +1,32 @@ /*- - * Copyright © 2009 + * Copyright (c) 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices * are retained or reproduced in an accompanying document, permission - * is granted to deal in this work without restriction, including un‐ + * is granted to deal in this work without restriction, including un- * limited rights to use, publicly perform, distribute, sell, modify, * merge, give away, or sublicence. * - * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to * the utmost extent permitted by applicable law, neither express nor * implied; without malicious intent or gross negligence. In no event * may a licensor, author or contributor be held liable for indirect, * direct, other damage, loss, or other issues arising in any way out * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out - * of said person’s immediate fault when using the work as intended. + * of said person's immediate fault when using the work as intended. */ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.11 2009/08/08 13:08:51 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.19 2011/09/07 15:24:16 tg Exp $"); /* build with CPPFLAGS+= -DUSE_REALLOC_MALLOC=0 on ancient systems */ #if defined(USE_REALLOC_MALLOC) && (USE_REALLOC_MALLOC == 0) -#define remalloc(p,n) ((p) == NULL ? malloc(n) : realloc((p), (n))) +#define remalloc(p,n) ((p) == NULL ? malloc_osi(n) : realloc_osi((p), (n))) #else -#define remalloc(p,n) realloc((p), (n)) +#define remalloc(p,n) realloc_osi((p), (n)) #endif #define ALLOC_ISUNALIGNED(p) (((ptrdiff_t)(p)) % ALLOC_SIZE) @@ -61,12 +61,27 @@ findptr(ALLOC_ITEM **lpp, char *ptr, Area *ap) #ifndef MKSH_SMALL fail: #endif - internal_errorf("rogue pointer %p", ptr); +#ifdef DEBUG + internal_warningf("rogue pointer %zX in ap %zX", + (size_t)ptr, (size_t)ap); + /* try to get a coredump */ + abort(); +#else + internal_errorf("rogue pointer %zX", (size_t)ptr); +#endif } return (ap); } void * +aresize2(void *ptr, size_t fac1, size_t fac2, Area *ap) +{ + if (notoktomul(fac1, fac2)) + internal_errorf(Tintovfl, fac1, '*', fac2); + return (aresize(ptr, fac1 * fac2, ap)); +} + +void * aresize(void *ptr, size_t numb, Area *ap) { ALLOC_ITEM *lp = NULL; @@ -79,14 +94,13 @@ aresize(void *ptr, size_t numb, Area *ap) pp->next = lp->next; } - if ((numb >= SIZE_MAX - ALLOC_SIZE) || + if (notoktoadd(numb, ALLOC_SIZE) || (lp = remalloc(lp, numb + ALLOC_SIZE)) == NULL #ifndef MKSH_SMALL || ALLOC_ISUNALIGNED(lp) #endif ) - internal_errorf("cannot allocate %lu data bytes", - (unsigned long)numb); + internal_errorf(Toomem, (unsigned long)numb); /* this only works because Area is an ALLOC_ITEM */ lp->next = ap->next; ap->next = lp; @@ -104,7 +118,7 @@ afree(void *ptr, Area *ap) /* unhook */ pp->next = lp->next; /* now free ALLOC_ITEM */ - free(lp); + free_osimalloc(lp); } } @@ -118,6 +132,6 @@ afreeall(Area *ap) /* make next ALLOC_ITEM head of list */ ap->next = lp->next; /* free old head */ - free(lp); + free_osimalloc(lp); } } @@ -1,7 +1,7 @@ -/* $OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $ */ +/* $OpenBSD: lex.c,v 1.45 2011/03/09 09:30:39 okan Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.156 2011/09/07 15:24:16 tg Exp $"); /* * states while lexing word @@ -35,78 +35,53 @@ __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $"); #define SEQUOTE 5 /* inside $'' */ #define SBRACE 6 /* inside ${} */ #define SQBRACE 7 /* inside "${}" */ -#define SCSPAREN 8 /* inside $() */ -#define SBQUOTE 9 /* inside `` */ -#define SASPAREN 10 /* inside $(( )) */ -#define SHEREDELIM 11 /* parsing <<,<<- delimiter */ -#define SHEREDQUOTE 12 /* parsing " in <<,<<- delimiter */ -#define SPATTERN 13 /* parsing *(...|...) pattern (*+?@!) */ -#define STBRACE 14 /* parsing ${...[#%]...} */ -#define SLETARRAY 15 /* inside =( ), just copy */ -#define SADELIM 16 /* like SBASE, looking for delimiter */ -#define SHERESTRING 17 /* parsing <<< string */ - -/* Structure to keep track of the lexing state and the various pieces of info - * needed for each particular state. */ -typedef struct lex_state Lex_state; -struct lex_state { - int ls_state; - union { - /* $(...) */ - struct scsparen_info { - int nparen; /* count open parenthesis */ - int csstate; /* XXX remove */ -#define ls_scsparen ls_info.u_scsparen - } u_scsparen; - - /* $((...)) */ - struct sasparen_info { - int nparen; /* count open parenthesis */ - int start; /* marks start of $(( in output str */ -#define ls_sasparen ls_info.u_sasparen - } u_sasparen; - - /* ((...)) */ - struct sletparen_info { - int nparen; /* count open parenthesis */ -#define ls_sletparen ls_info.u_sletparen - } u_sletparen; - - /* `...` */ - struct sbquote_info { - int indquotes; /* true if in double quotes: "`...`" */ -#define ls_sbquote ls_info.u_sbquote - } u_sbquote; - -#ifndef MKSH_SMALL - /* =(...) */ - struct sletarray_info { - int nparen; /* count open parentheses */ -#define ls_sletarray ls_info.u_sletarray - } u_sletarray; -#endif +#define SBQUOTE 8 /* inside `` */ +#define SASPAREN 9 /* inside $(( )) */ +#define SHEREDELIM 10 /* parsing <<,<<- delimiter */ +#define SHEREDQUOTE 11 /* parsing " in <<,<<- delimiter */ +#define SPATTERN 12 /* parsing *(...|...) pattern (*+?@!) */ +#define SADELIM 13 /* like SBASE, looking for delimiter */ +#define SHERESTRING 14 /* parsing <<< string */ +#define STBRACEKORN 15 /* parsing ${...[#%]...} !FSH */ +#define STBRACEBOURNE 16 /* parsing ${...[#%]...} FSH */ +#define SINVALID 255 /* invalid state */ + +struct sretrace_info { + struct sretrace_info *next; + XString xs; + char *xp; +}; - /* ADELIM */ - struct sadelim_info { - unsigned char nparen; /* count open parentheses */ -#define SADELIM_BASH 0 -#define SADELIM_MAKE 1 - unsigned char style; +/* + * Structure to keep track of the lexing state and the various pieces of info + * needed for each particular state. + */ +typedef struct lex_state { + union { + /* point to the next state block */ + struct lex_state *base; + /* marks start of state output in output string */ + int start; + /* SBQUOTE: true if in double quotes: "`...`" */ + /* SEQUOTE: got NUL, ignore rest of string */ + bool abool; + /* SADELIM information */ + struct { + /* character to search for */ unsigned char delimiter; + /* max. number of delimiters */ unsigned char num; - unsigned char flags; /* ofs. into sadelim_flags[] */ -#define ls_sadelim ls_info.u_sadelim - } u_sadelim; - - /* $'...' */ - struct sequote_info { - bool got_NUL; /* ignore rest of string */ -#define ls_sequote ls_info.u_sequote - } u_sequote; - - Lex_state *base; /* used to point to next state block */ - } ls_info; -}; + } adelim; + } u; + /* count open parentheses */ + short nparen; + /* type of this state */ + uint8_t type; +} Lex_state; +#define ls_base u.base +#define ls_start u.start +#define ls_bool u.abool +#define ls_adelim u.adelim typedef struct { Lex_state *base; @@ -114,72 +89,106 @@ typedef struct { } State_info; static void readhere(struct ioword *); -static int getsc__(void); +static void ungetsc(int); +static void ungetsc_(int); +static int getsc_uu(void); static void getsc_line(Source *); static int getsc_bn(void); static int s_get(void); static void s_put(int); static char *get_brace_var(XString *, char *); -static int arraysub(char **); -static const char *ungetsc(int); +static bool arraysub(char **); static void gethere(bool); static Lex_state *push_state_(State_info *, Lex_state *); static Lex_state *pop_state_(State_info *, Lex_state *); static int dopprompt(const char *, int, bool); +void yyskiputf8bom(void); static int backslash_skip; static int ignore_backslash_newline; +static struct sretrace_info *retrace_info; +short subshell_nesting_level = 0; /* optimised getsc_bn() */ -#define _getsc() (*source->str != '\0' && *source->str != '\\' \ - && !backslash_skip && !(source->flags & SF_FIRST) \ - ? *source->str++ : getsc_bn()) -/* optimised getsc__() */ -#define _getsc_() ((*source->str != '\0') && !(source->flags & SF_FIRST) \ - ? *source->str++ : getsc__()) +#define o_getsc() (*source->str != '\0' && *source->str != '\\' && \ + !backslash_skip ? *source->str++ : getsc_bn()) +/* optimised getsc_uu() */ +#define o_getsc_u() ((*source->str != '\0') ? *source->str++ : getsc_uu()) + +/* retrace helper */ +#define o_getsc_r(carg) { \ + int cev = (carg); \ + struct sretrace_info *rp = retrace_info; \ + \ + while (rp) { \ + Xcheck(rp->xs, rp->xp); \ + *rp->xp++ = cev; \ + rp = rp->next; \ + } \ + \ + return (cev); \ +} #ifdef MKSH_SMALL static int getsc(void); -static int getsc_(void); static int getsc(void) { - return (_getsc()); + o_getsc_r(o_getsc()); } +#else +static int getsc_r(int); static int -getsc_(void) +getsc_r(int c) { - return (_getsc_()); + o_getsc_r(c); } -#else -/* !MKSH_SMALL: use them inline */ -#define getsc() _getsc() -#define getsc_() _getsc_() + +#define getsc() getsc_r(o_getsc()) #endif -#define STATE_BSIZE 32 +#define STATE_BSIZE 8 #define PUSH_STATE(s) do { \ if (++statep == state_info.end) \ statep = push_state_(&state_info, statep); \ - state = statep->ls_state = (s); \ -} while (0) + state = statep->type = (s); \ +} while (/* CONSTCOND */ 0) #define POP_STATE() do { \ if (--statep == state_info.base) \ statep = pop_state_(&state_info, statep); \ - state = statep->ls_state; \ -} while (0) + state = statep->type; \ +} while (/* CONSTCOND */ 0) + +#define PUSH_SRETRACE() do { \ + struct sretrace_info *ri; \ + \ + statep->ls_start = Xsavepos(ws, wp); \ + ri = alloc(sizeof(struct sretrace_info), ATEMP); \ + Xinit(ri->xs, ri->xp, 64, ATEMP); \ + ri->next = retrace_info; \ + retrace_info = ri; \ +} while (/* CONSTCOND */ 0) + +#define POP_SRETRACE() do { \ + wp = Xrestpos(ws, wp, statep->ls_start); \ + *retrace_info->xp = '\0'; \ + sp = Xstring(retrace_info->xs, retrace_info->xp); \ + dp = (void *)retrace_info; \ + retrace_info = retrace_info->next; \ + afree(dp, ATEMP); \ +} while (/* CONSTCOND */ 0) /** * Lexical analyser * * tokens are not regular expressions, they are LL(1). * for example, "${var:-${PWD}}", and "$(size $(whence ksh))". - * hence the state stack. + * hence the state stack. Note "$(...)" are now parsed recursively. */ int @@ -188,13 +197,14 @@ yylex(int cf) Lex_state states[STATE_BSIZE], *statep, *s2, *base; State_info state_info; int c, c2, state; + size_t cz; XString ws; /* expandable output word */ char *wp; /* output word pointer */ char *sp, *dp; Again: - states[0].ls_state = -1; - states[0].ls_info.base = NULL; + states[0].type = SINVALID; + states[0].ls_base = NULL; statep = &states[1]; state_info.base = states; state_info.end = &state_info.base[STATE_BSIZE]; @@ -204,19 +214,15 @@ yylex(int cf) backslash_skip = 0; ignore_backslash_newline = 0; - if (cf&ONEWORD) + if (cf & ONEWORD) state = SWORD; - else if (cf&LETEXPR) { + else if (cf & LETEXPR) { /* enclose arguments in (double) quotes */ *wp++ = OQUOTE; state = SLETPAREN; - statep->ls_sletparen.nparen = 0; -#ifndef MKSH_SMALL - } else if (cf&LETARRAY) { - state = SLETARRAY; - statep->ls_sletarray.nparen = 0; -#endif - } else { /* normal lexing */ + statep->nparen = 0; + } else { + /* normal lexing */ state = (cf & HEREDELIM) ? SHEREDELIM : SBASE; while ((c = getsc()) == ' ' || c == '\t') ; @@ -228,13 +234,14 @@ yylex(int cf) } ungetsc(c); } - if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */ + if (source->flags & SF_ALIAS) { + /* trailing ' ' in alias definition */ source->flags &= ~SF_ALIAS; cf |= ALIAS; } - /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */ - statep->ls_state = state; + /* Initial state: one of SWORD SLETPAREN SHEREDELIM SBASE */ + statep->type = state; /* check for here string */ if (state == SHEREDELIM) { @@ -259,14 +266,14 @@ yylex(int cf) switch (state) { case SADELIM: if (c == '(') - statep->ls_sadelim.nparen++; + statep->nparen++; else if (c == ')') - statep->ls_sadelim.nparen--; - else if (statep->ls_sadelim.nparen == 0 && - (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) { + statep->nparen--; + else if (statep->nparen == 0 && + (c == /*{*/ '}' || c == statep->ls_adelim.delimiter)) { *wp++ = ADELIM; *wp++ = c; - if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0) + if (c == /*{*/ '}' || --statep->ls_adelim.num == 0) POP_STATE(); if (c == /*{*/ '}') POP_STATE(); @@ -275,7 +282,8 @@ yylex(int cf) /* FALLTHROUGH */ case SBASE: if (c == '[' && (cf & (VARASN|ARRAYVAR))) { - *wp = EOS; /* temporary */ + /* temporary */ + *wp = EOS; if (is_wdvarname(Xstring(ws, wp), false)) { char *p, *tmp; @@ -378,17 +386,20 @@ yylex(int cf) if (c == '(') /*)*/ { c = getsc(); if (c == '(') /*)*/ { - PUSH_STATE(SASPAREN); - statep->ls_sasparen.nparen = 2; - statep->ls_sasparen.start = - Xsavepos(ws, wp); *wp++ = EXPRSUB; + PUSH_STATE(SASPAREN); + statep->nparen = 2; + PUSH_SRETRACE(); + *retrace_info->xp++ = '('; } else { ungetsc(c); - PUSH_STATE(SCSPAREN); - statep->ls_scsparen.nparen = 1; - statep->ls_scsparen.csstate = 0; + subst_command: + sp = yyrecursive(); + cz = strlen(sp) + 1; + XcheckN(ws, wp, cz); *wp++ = COMSUB; + memcpy(wp, sp, cz); + wp += cz; } } else if (c == '{') /*}*/ { *wp++ = OSUBST; @@ -407,14 +418,14 @@ yylex(int cf) *wp++ = ':'; PUSH_STATE(SBRACE); PUSH_STATE(SADELIM); - statep->ls_sadelim.style = SADELIM_BASH; - statep->ls_sadelim.delimiter = ':'; - statep->ls_sadelim.num = 1; - statep->ls_sadelim.nparen = 0; + statep->ls_adelim.delimiter = ':'; + statep->ls_adelim.num = 1; + statep->nparen = 0; break; } else if (ksh_isdigit(c) || c == '('/*)*/ || c == ' ' || - c == '$' /* XXX what else? */) { + /*XXX what else? */ + c == '$') { /* substring subst. */ if (c != ' ') { *wp++ = CHAR; @@ -423,10 +434,9 @@ yylex(int cf) ungetsc(c); PUSH_STATE(SBRACE); PUSH_STATE(SADELIM); - statep->ls_sadelim.style = SADELIM_BASH; - statep->ls_sadelim.delimiter = ':'; - statep->ls_sadelim.num = 2; - statep->ls_sadelim.nparen = 0; + statep->ls_adelim.delimiter = ':'; + statep->ls_adelim.num = 2; + statep->nparen = 0; break; } } else if (c == '/') { @@ -439,18 +449,21 @@ yylex(int cf) ungetsc(c); PUSH_STATE(SBRACE); PUSH_STATE(SADELIM); - statep->ls_sadelim.style = SADELIM_BASH; - statep->ls_sadelim.delimiter = '/'; - statep->ls_sadelim.num = 1; - statep->ls_sadelim.nparen = 0; + statep->ls_adelim.delimiter = '/'; + statep->ls_adelim.num = 1; + statep->nparen = 0; break; } - /* If this is a trim operation, + /* + * If this is a trim operation, * treat (,|,) specially in STBRACE. */ if (ctype(c, C_SUBOP2)) { ungetsc(c); - PUSH_STATE(STBRACE); + if (Flag(FSH)) + PUSH_STATE(STBRACEBOURNE); + else + PUSH_STATE(STBRACEKORN); } else { ungetsc(c); if (state == SDQUOTE) @@ -483,11 +496,15 @@ yylex(int cf) *wp++ = OQUOTE; ignore_backslash_newline++; PUSH_STATE(SEQUOTE); - statep->ls_sequote.got_NUL = false; + statep->ls_bool = false; break; + } else if (c == '"' && (state == SBASE)) { + /* XXX which other states are valid? */ + goto DEQUOTE; } else { *wp++ = CHAR; *wp++ = '$'; + DEQUOTE: ungetsc(c); } break; @@ -495,7 +512,8 @@ yylex(int cf) subst_gravis: PUSH_STATE(SBQUOTE); *wp++ = COMSUB; - /* Need to know if we are inside double quotes + /* + * Need to know if we are inside double quotes * since sh/AT&T-ksh translate the \" to " in * "`...\"...`". * This is not done in POSIX mode (section @@ -515,19 +533,19 @@ yylex(int cf) * literal meaning, except when followed by * $ ` \."). */ - statep->ls_sbquote.indquotes = 0; + statep->ls_bool = false; s2 = statep; base = state_info.base; - while (1) { + while (/* CONSTCOND */ 1) { for (; s2 != base; s2--) { - if (s2->ls_state == SDQUOTE) { - statep->ls_sbquote.indquotes = 1; + if (s2->type == SDQUOTE) { + statep->ls_bool = true; break; } } if (s2 != base) break; - if (!(s2 = s2->ls_info.base)) + if (!(s2 = s2->ls_base)) break; base = s2-- - STATE_BSIZE; } @@ -555,23 +573,23 @@ yylex(int cf) if ((c2 = unbksl(true, s_get, s_put)) == -1) c2 = s_get(); if (c2 == 0) - statep->ls_sequote.got_NUL = true; - if (!statep->ls_sequote.got_NUL) { + statep->ls_bool = true; + if (!statep->ls_bool) { char ts[4]; if ((unsigned int)c2 < 0x100) { *wp++ = QCHAR; *wp++ = c2; } else { - c = utf_wctomb(ts, c2 - 0x100); - ts[c] = 0; - for (c = 0; ts[c]; ++c) { + cz = utf_wctomb(ts, c2 - 0x100); + ts[cz] = 0; + for (cz = 0; ts[cz]; ++cz) { *wp++ = QCHAR; - *wp++ = ts[c]; + *wp++ = ts[cz]; } } } - } else if (!statep->ls_sequote.got_NUL) { + } else if (!statep->ls_bool) { *wp++ = QCHAR; *wp++ = c; } @@ -596,97 +614,47 @@ yylex(int cf) goto Subst; break; - case SCSPAREN: /* $( ... ) */ - /* todo: deal with $(...) quoting properly - * kludge to partly fake quoting inside $(...): doesn't - * really work because nested $(...) or ${...} inside - * double quotes aren't dealt with. - */ - switch (statep->ls_scsparen.csstate) { - case 0: /* normal */ - switch (c) { - case '(': - statep->ls_scsparen.nparen++; - break; - case ')': - statep->ls_scsparen.nparen--; - break; - case '\\': - statep->ls_scsparen.csstate = 1; - break; - case '"': - statep->ls_scsparen.csstate = 2; - break; - case '\'': - statep->ls_scsparen.csstate = 4; - ignore_backslash_newline++; - break; - } - break; - - case 1: /* backslash in normal mode */ - case 3: /* backslash in double quotes */ - --statep->ls_scsparen.csstate; - break; - - case 2: /* double quotes */ - if (c == '"') - statep->ls_scsparen.csstate = 0; - else if (c == '\\') - statep->ls_scsparen.csstate = 3; - break; - - case 4: /* single quotes */ - if (c == '\'') { - statep->ls_scsparen.csstate = 0; - ignore_backslash_newline--; - } - break; - } - if (statep->ls_scsparen.nparen == 0) { - POP_STATE(); - *wp++ = 0; /* end of COMSUB */ - } else - *wp++ = c; - break; - - case SASPAREN: /* $(( ... )) */ - /* XXX should nest using existing state machine - * (embed "...", $(...), etc.) */ + /* $(( ... )) */ + case SASPAREN: if (c == '(') - statep->ls_sasparen.nparen++; + statep->nparen++; else if (c == ')') { - statep->ls_sasparen.nparen--; - if (statep->ls_sasparen.nparen == 1) { - /*(*/ - if ((c2 = getsc()) == ')') { - POP_STATE(); - /* end of EXPRSUB */ - *wp++ = 0; + statep->nparen--; + if (statep->nparen == 1) { + /* end of EXPRSUB */ + POP_SRETRACE(); + POP_STATE(); + + if ((c2 = getsc()) == /*(*/ ')') { + cz = strlen(sp) - 2; + XcheckN(ws, wp, cz); + memcpy(wp, sp + 1, cz); + wp += cz; + afree(sp, ATEMP); + *wp++ = '\0'; break; } else { - char *s; + Source *s; ungetsc(c2); - /* mismatched parenthesis - + /* + * mismatched parenthesis - * assume we were really * parsing a $(...) expression */ - s = Xrestpos(ws, wp, - statep->ls_sasparen.start); - memmove(s + 1, s, wp - s); - *s++ = COMSUB; - *s = '('; /*)*/ - wp++; - statep->ls_scsparen.nparen = 1; - statep->ls_scsparen.csstate = 0; - state = statep->ls_state = - SCSPAREN; + --wp; + s = pushs(SREREAD, + source->areap); + s->start = s->str = + s->u.freeme = sp; + s->next = source; + source = s; + goto subst_command; } } } - *wp++ = c; - break; + /* reuse existing state machine */ + goto Sbase2; case SQBRACE: if (c == '\\') { @@ -724,18 +692,21 @@ yylex(int cf) *wp++ = /*{*/ '}'; break; - case STBRACE: - /* Same as SBASE, except (,|,) treated specially */ - if (c == /*{*/ '}') { - POP_STATE(); - *wp++ = CSUBST; - *wp++ = /*{*/ '}'; - } else if (c == '|') { + /* Same as SBASE, except (,|,) treated specially */ + case STBRACEKORN: + if (c == '|') *wp++ = SPAT; - } else if (c == '(') { + else if (c == '(') { *wp++ = OPAT; - *wp++ = ' '; /* simile for @ */ + /* simile for @ */ + *wp++ = ' '; PUSH_STATE(SPATTERN); + } else /* FALLTHROUGH */ + case STBRACEBOURNE: + if (c == /*{*/ '}') { + POP_STATE(); + *wp++ = CSUBST; + *wp++ = /*{*/ '}'; } else goto Sbase1; break; @@ -746,36 +717,37 @@ yylex(int cf) POP_STATE(); } else if (c == '\\') { switch (c = getsc()) { + case 0: + /* trailing \ is lost */ + break; case '\\': case '$': case '`': *wp++ = c; break; case '"': - if (statep->ls_sbquote.indquotes) { + if (statep->ls_bool) { *wp++ = c; break; } /* FALLTHROUGH */ default: - if (c) { - /* trailing \ is lost */ - *wp++ = '\\'; - *wp++ = c; - } + *wp++ = '\\'; + *wp++ = c; break; } } else *wp++ = c; break; - case SWORD: /* ONEWORD */ + /* ONEWORD */ + case SWORD: goto Subst; - case SLETPAREN: /* LETEXPR: (( ... )) */ - /*(*/ - if (c == ')') { - if (statep->ls_sletparen.nparen > 0) - --statep->ls_sletparen.nparen; + /* LETEXPR: (( ... )) */ + case SLETPAREN: + if (c == /*(*/ ')') { + if (statep->nparen > 0) + --statep->nparen; else if ((c2 = getsc()) == /*(*/ ')') { c = 0; *wp++ = CQUOTE; @@ -784,13 +756,14 @@ yylex(int cf) Source *s; ungetsc(c2); - /* mismatched parenthesis - + /* + * mismatched parenthesis - * assume we were really - * parsing a $(...) expression + * parsing a (...) expression */ *wp = EOS; sp = Xstring(ws, wp); - dp = wdstrip(sp, true, false); + dp = wdstrip(sp, WDS_KEEPQ); s = pushs(SREREAD, source->areap); s->start = s->str = s->u.freeme = dp; s->next = source; @@ -798,28 +771,16 @@ yylex(int cf) return ('('/*)*/); } } else if (c == '(') - /* parenthesis inside quotes and backslashes - * are lost, but AT&T ksh doesn't count them - * either + /* + * parentheses inside quotes and + * backslashes are lost, but AT&T ksh + * doesn't count them either */ - ++statep->ls_sletparen.nparen; + ++statep->nparen; goto Sbase2; -#ifndef MKSH_SMALL - case SLETARRAY: /* LETARRAY: =( ... ) */ - if (c == '('/*)*/) - ++statep->ls_sletarray.nparen; - else if (c == /*(*/')') - if (statep->ls_sletarray.nparen-- == 0) { - c = 0; - goto Done; - } - *wp++ = CHAR; - *wp++ = c; - break; -#endif - - case SHERESTRING: /* <<< delimiter */ + /* <<< delimiter */ + case SHERESTRING: if (c == '\\') { c = getsc(); if (c) { @@ -827,14 +788,13 @@ yylex(int cf) *wp++ = QCHAR; *wp++ = c; } - /* invoke quoting mode */ - Xstring(ws, wp)[0] = QCHAR; } else if (c == '$') { if ((c2 = getsc()) == '\'') { PUSH_STATE(SEQUOTE); - statep->ls_sequote.got_NUL = false; + statep->ls_bool = false; goto sherestring_quoted; - } + } else if (c2 == '"') + goto sherestring_dquoted; ungetsc(c2); goto sherestring_regular; } else if (c == '\'') { @@ -842,10 +802,9 @@ yylex(int cf) sherestring_quoted: *wp++ = OQUOTE; ignore_backslash_newline++; - /* invoke quoting mode */ - Xstring(ws, wp)[0] = QCHAR; } else if (c == '"') { - state = statep->ls_state = SHEREDQUOTE; + sherestring_dquoted: + state = statep->type = SHEREDQUOTE; *wp++ = OQUOTE; /* just don't IFS split; no quoting mode */ } else { @@ -855,13 +814,16 @@ yylex(int cf) } break; - case SHEREDELIM: /* <<,<<- delimiter */ - /* XXX chuck this state (and the next) - use + /* <<,<<- delimiter */ + case SHEREDELIM: + /* + * XXX chuck this state (and the next) - use * the existing states ($ and \`...` should be * stripped of their specialness after the * fact). */ - /* here delimiters need a special case since + /* + * here delimiters need a special case since * $ and `...` are not to be treated specially */ if (c == '\\') { @@ -874,9 +836,10 @@ yylex(int cf) } else if (c == '$') { if ((c2 = getsc()) == '\'') { PUSH_STATE(SEQUOTE); - statep->ls_sequote.got_NUL = false; + statep->ls_bool = false; goto sheredelim_quoted; - } + } else if (c2 == '"') + goto sheredelim_dquoted; ungetsc(c2); goto sheredelim_regular; } else if (c == '\'') { @@ -885,7 +848,8 @@ yylex(int cf) *wp++ = OQUOTE; ignore_backslash_newline++; } else if (c == '"') { - state = statep->ls_state = SHEREDQUOTE; + sheredelim_dquoted: + state = statep->type = SHEREDQUOTE; *wp++ = OQUOTE; } else { sheredelim_regular: @@ -894,25 +858,27 @@ yylex(int cf) } break; - case SHEREDQUOTE: /* " in <<,<<- delimiter */ + /* " in <<,<<- delimiter */ + case SHEREDQUOTE: if (c == '"') { *wp++ = CQUOTE; - state = statep->ls_state = + state = statep->type = /* dp[1] == '<' means here string */ Xstring(ws, wp)[1] == '<' ? SHERESTRING : SHEREDELIM; } else { if (c == '\\') { switch (c = getsc()) { - case '\\': case '"': - case '$': case '`': + case 0: + /* trailing \ is lost */ + case '\\': + case '"': + case '$': + case '`': break; default: - if (c) { - /* trailing \ lost */ - *wp++ = CHAR; - *wp++ = '\\'; - } + *wp++ = CHAR; + *wp++ = '\\'; break; } } @@ -921,15 +887,17 @@ yylex(int cf) } break; - case SPATTERN: /* in *(...|...) pattern (*+?@!) */ - if ( /*(*/ c == ')') { + /* in *(...|...) pattern (*+?@!) */ + case SPATTERN: + if (c == /*(*/ ')') { *wp++ = CPAT; POP_STATE(); } else if (c == '|') { *wp++ = SPAT; } else if (c == '(') { *wp++ = OPAT; - *wp++ = ' '; /* simile for @ */ + /* simile for @ */ + *wp++ = ' '; PUSH_STATE(SPATTERN); } else goto Sbase1; @@ -942,11 +910,6 @@ yylex(int cf) /* XXX figure out what is missing */ yyerror("no closing quote\n"); -#ifndef MKSH_SMALL - if (state == SLETARRAY && statep->ls_sletarray.nparen != -1) - yyerror("%s: ')' missing\n", T_synerr); -#endif - /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ if (state == SHEREDELIM || state == SHERESTRING) state = SBASE; @@ -984,10 +947,14 @@ yylex(int cf) iop->flag |= c == c2 ? (c == '>' ? IOCAT : IOHERE) : IORDWR; if (iop->flag == IOHERE) { - if ((c2 = getsc()) == '-') + if ((c2 = getsc()) == '-') { iop->flag |= IOSKIP; - else - ungetsc(c2); + c2 = getsc(); + } else if (c2 == '<') + iop->flag |= IOHERESTR; + ungetsc(c2); + if (c2 == '\n') + iop->flag |= IONDELIM; } } else if (c2 == '&') iop->flag |= IODUP | (c == '<' ? IORDUP : 0); @@ -1002,15 +969,17 @@ yylex(int cf) iop->name = NULL; iop->delim = NULL; iop->heredoc = NULL; - Xfree(ws, wp); /* free word */ + /* free word */ + Xfree(ws, wp); yylval.iop = iop; return (REDIR); no_iop: - ; + afree(iop, ATEMP); } if (wp == dp && state == SBASE) { - Xfree(ws, wp); /* free word */ + /* free word */ + Xfree(ws, wp); /* no word, process LEX1 character */ if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) { if ((c2 = getsc()) == c) @@ -1020,8 +989,20 @@ yylex(int cf) /* c == '(' ) */ MDPAREN; else if (c == '|' && c2 == '&') c = COPROC; + else if (c == ';' && c2 == '|') + c = BRKEV; + else if (c == ';' && c2 == '&') + c = BRKFT; else ungetsc(c2); +#ifndef MKSH_SMALL + if (c == BREAK) { + if ((c2 = getsc()) == '&') + c = BRKEV; + else + ungetsc(c2); + } +#endif } else if (c == '\n') { gethere(false); if (cf & CONTIN) @@ -1032,14 +1013,11 @@ yylex(int cf) return (c); } - *wp++ = EOS; /* terminate word */ + /* terminate word */ + *wp++ = EOS; yylval.cp = Xclose(ws, wp); if (state == SWORD || state == SLETPAREN - /* XXX ONEWORD? */ -#ifndef MKSH_SMALL - || state == SLETARRAY -#endif - ) + /* XXX ONEWORD? */) return (LWORD); /* unget terminator */ @@ -1067,15 +1045,16 @@ yylex(int cf) /* Make sure the ident array stays '\0' padded */ memset(dp, 0, (ident+IDENT) - dp + 1); if (c != EOS) - *ident = '\0'; /* word is not unquoted */ + /* word is not unquoted */ + *ident = '\0'; - if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) { + if (*ident != '\0' && (cf & (KEYWORD | ALIAS))) { struct tbl *p; uint32_t h = hash(ident); - /* { */ if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) && - (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) { + (!(cf & ESACONLY) || p->val.i == ESAC || + p->val.i == /*{*/ '}')) { afree(yylval.cp, ATEMP); return (p->val.i); } @@ -1101,13 +1080,7 @@ yylex(int cf) */ ++cp; /* prefer functions over aliases */ - if (*cp == '(' /*)*/) - /* - * delete alias upon encountering function - * definition - */ - ktdelete(p); - else { + if (cp[0] != '(' || cp[1] != ')') { Source *s = source; while (s && (s->flags & SF_HASALIAS)) @@ -1142,7 +1115,7 @@ gethere(bool iseof) struct ioword **p; for (p = heres; p < herep; p++) - if (iseof && (*p)->delim[1] != '<') + if (iseof && !((*p)->flag & IOHERESTR)) /* only here strings at EOF */ return; else @@ -1158,62 +1131,90 @@ static void readhere(struct ioword *iop) { int c; - char *volatile eof; - char *eofp; - int skiptabs; + const char *eof, *eofp; XString xs; char *xp; int xpos; - if (iop->delim[1] == '<') { + if (iop->flag & IOHERESTR) { /* process the here string */ - xp = iop->heredoc = evalstr(iop->delim, DOBLANK); - c = strlen(xp) - 1; - memmove(xp, xp + 1, c); - xp[c] = '\n'; + iop->heredoc = xp = evalstr(iop->delim, DOBLANK); + xpos = strlen(xp) - 1; + memmove(xp, xp + 1, xpos); + xp[xpos] = '\n'; return; } - eof = evalstr(iop->delim, 0); + eof = iop->flag & IONDELIM ? "<<" : evalstr(iop->delim, 0); if (!(iop->flag & IOEVAL)) ignore_backslash_newline++; Xinit(xs, xp, 256, ATEMP); - for (;;) { - eofp = eof; - skiptabs = iop->flag & IOSKIP; - xpos = Xsavepos(xs, xp); - while ((c = getsc()) != 0) { - if (skiptabs) { - if (c == '\t') - continue; - skiptabs = 0; - } - if (c != *eofp) + heredoc_read_line: + /* beginning of line */ + eofp = eof; + xpos = Xsavepos(xs, xp); + if (iop->flag & IOSKIP) { + /* skip over leading tabs */ + while ((c = getsc()) == '\t') + /* nothing */; + goto heredoc_parse_char; + } + heredoc_read_char: + c = getsc(); + heredoc_parse_char: + /* compare with here document marker */ + if (!*eofp) { + /* end of here document marker, what to do? */ + switch (c) { + case /*(*/ ')': + if (!subshell_nesting_level) + /*- + * not allowed outside $(...) or (...) + * => mismatch + */ break; - Xcheck(xs, xp); - Xput(xs, xp, c); - eofp++; - } - /* Allow EOF here so commands with out trailing newlines - * will work (eg, ksh -c '...', $(...), etc). - */ - if (*eofp == '\0' && (c == 0 || c == '\n')) { - xp = Xrestpos(xs, xp, xpos); - break; - } - ungetsc(c); - while ((c = getsc()) != '\n') { - if (c == 0) - yyerror("here document '%s' unclosed\n", eof); - Xcheck(xs, xp); - Xput(xs, xp, c); + /* allow $(...) or (...) to close here */ + ungetsc(/*(*/ ')'); + /* FALLTHROUGH */ + case 0: + /* + * Allow EOF here to commands without trailing + * newlines (mksh -c '...') will work as well. + */ + case '\n': + /* Newline terminates here document marker */ + goto heredoc_found_terminator; } + } else if (c == *eofp++) + /* store; then read and compare next character */ + goto heredoc_store_and_loop; + /* nope, mismatch; read until end of line */ + while (c != '\n') { + if (!c) + /* oops, reached EOF */ + yyerror("%s '%s' unclosed\n", "here document", eof); + /* store character */ Xcheck(xs, xp); Xput(xs, xp, c); + /* read next character */ + c = getsc(); } + /* we read a newline as last character */ + heredoc_store_and_loop: + /* store character */ + Xcheck(xs, xp); + Xput(xs, xp, c); + if (c == '\n') + goto heredoc_read_line; + goto heredoc_read_char; + + heredoc_found_terminator: + /* jump back to saved beginning of line */ + xp = Xrestpos(xs, xp, xpos); + /* terminate, close and store */ Xput(xs, xp, '\0'); iop->heredoc = Xclose(xs, xp); @@ -1229,7 +1230,8 @@ yyerror(const char *fmt, ...) /* pop aliases and re-reads */ while (source->type == SALIAS || source->type == SREREAD) source = source->next; - source->str = null; /* zap pending input */ + /* zap pending input */ + source->str = null; error_prefix(true); va_start(va, fmt); @@ -1258,14 +1260,14 @@ pushs(int type, Area *areap) } static int -getsc__(void) +getsc_uu(void) { Source *s = source; int c; - getsc_again: while ((c = *s->str++) == 0) { - s->str = NULL; /* return 0 for EOF by default */ + /* return 0 for EOF by default */ + s->str = NULL; switch (s->type) { case SEOF: s->str = null; @@ -1305,25 +1307,28 @@ getsc__(void) s = source; } else if (*s->u.tblp->val.s && (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) { - source = s = s->next; /* pop source stack */ - /* Note that this alias ended with a space, - * enabling alias expansion on the following - * word. + /* pop source stack */ + source = s = s->next; + /* + * Note that this alias ended with a + * space, enabling alias expansion on + * the following word. */ s->flags |= SF_ALIAS; } else { - /* At this point, we need to keep the current + /* + * At this point, we need to keep the current * alias in the source list so recursive - * aliases can be detected and we also need - * to return the next character. Do this - * by temporarily popping the alias to get - * the next character and then put it back - * in the source list with the SF_ALIASEND - * flag set. + * aliases can be detected and we also need to + * return the next character. Do this by + * temporarily popping the alias to get the + * next character and then put it back in the + * source list with the SF_ALIASEND flag set. */ - source = s->next; /* pop source stack */ + /* pop source stack */ + source = s->next; source->flags |= s->flags & SF_ALIAS; - c = getsc__(); + c = getsc_uu(); if (c) { s->flags |= SF_ALIASEND; s->ugbuf[0] = c; s->ugbuf[1] = '\0'; @@ -1332,7 +1337,7 @@ getsc__(void) source = s; } else { s = source; - /* avoid reading eof twice */ + /* avoid reading EOF twice */ s->str = NULL; break; } @@ -1340,7 +1345,8 @@ getsc__(void) continue; case SREREAD: - if (s->start != s->ugbuf) /* yuck */ + if (s->start != s->ugbuf) + /* yuck */ afree(s->u.freeme, ATEMP); source = s = s->next; continue; @@ -1355,17 +1361,6 @@ getsc__(void) shf_flush(shl_out); } } - /* check for UTF-8 byte order mark */ - if (s->flags & SF_FIRST) { - s->flags &= ~SF_FIRST; - if (((unsigned char)c == 0xEF) && - (((const unsigned char *)(s->str))[0] == 0xBB) && - (((const unsigned char *)(s->str))[1] == 0xBF)) { - s->str += 2; - UTFMODE = 1; - goto getsc_again; - } - } return (c); } @@ -1395,7 +1390,8 @@ getsc_line(Source *s) int nread; nread = x_read(xp, LINE); - if (nread < 0) /* read error */ + if (nread < 0) + /* read error */ nread = 0; xp[nread] = '\0'; xp += nread; @@ -1405,7 +1401,7 @@ getsc_line(Source *s) else s->line++; - while (1) { + while (/* CONSTCOND */ 1) { char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf); if (!p && shf_error(s->u.shf) && @@ -1418,20 +1414,24 @@ getsc_line(Source *s) if (!p || (xp = p, xp[-1] == '\n')) break; /* double buffer size */ - xp++; /* move past NUL so doubling works... */ + /* move past NUL so doubling works... */ + xp++; XcheckN(s->xs, xp, Xlength(s->xs, xp)); - xp--; /* ...and move back again */ + /* ...and move back again */ + xp--; } - /* flush any unwanted input so other programs/builtins + /* + * flush any unwanted input so other programs/builtins * can read it. Not very optimal, but less error prone * than flushing else where, dealing with redirections, * etc. - * todo: reduce size of shf buffer (~128?) if SSTDIN + * TODO: reduce size of shf buffer (~128?) if SSTDIN */ if (s->type == SSTDIN) shf_flush(s->u.shf); } - /* XXX: temporary kludge to restore source after a + /* + * XXX: temporary kludge to restore source after a * trap may have been executed. */ source = s; @@ -1445,7 +1445,7 @@ getsc_line(Source *s) int linelen; linelen = Xlength(s->xs, xp); - XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1); + XcheckN(s->xs, xp, Zfc_e_dash + /* NUL */ 1); /* reload after potential realloc */ cp = Xstring(s->xs, xp); /* change initial '!' into space */ @@ -1453,10 +1453,10 @@ getsc_line(Source *s) /* NUL terminate the current string */ *xp = '\0'; /* move the actual string forward */ - memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1); - xp += fc_e_n; + memmove(cp + Zfc_e_dash, cp, linelen + /* NUL */ 1); + xp += Zfc_e_dash; /* prepend it with "fc -e -" */ - memcpy(cp, fc_e_, fc_e_n); + memcpy(cp, Tfc_e_dash, Zfc_e_dash); } #endif s->start = s->str = cp; @@ -1489,8 +1489,10 @@ set_prompt(int to, Source *s) cur_prompt = to; switch (to) { - case PS1: /* command */ - /* Substitute ! and !! here, before substitutions are done + /* command */ + case PS1: + /* + * Substitute ! and !! here, before substitutions are done * so ! in expanded variables are not expanded. * NOTE: this is not what AT&T ksh does (it does it after * substitutions, POSIX doesn't say which is to be done. @@ -1514,7 +1516,8 @@ set_prompt(int to, Source *s) newenv(E_ERRH); if (sigsetjmp(e->jbuf, 0)) { prompt = safe_prompt; - /* Don't print an error - assume it has already + /* + * Don't print an error - assume it has already * been printed. Reason is we may have forked * to run a command and the child may be * unwinding its stack through this code as it @@ -1527,7 +1530,8 @@ set_prompt(int to, Source *s) quitenv(NULL); } break; - case PS2: /* command continuation */ + /* command continuation */ + case PS2: prompt = str_val(global("PS2")); break; } @@ -1539,11 +1543,12 @@ dopprompt(const char *cp, int ntruncate, bool doprint) int columns = 0, lines = 0, indelimit = 0; char delimiter = 0; - /* Undocumented AT&T ksh feature: - * If the second char in the prompt string is \r then the first char - * is taken to be a non-printing delimiter and any chars between two - * instances of the delimiter are not considered to be part of the - * prompt length + /* + * Undocumented AT&T ksh feature: + * If the second char in the prompt string is \r then the first + * char is taken to be a non-printing delimiter and any chars + * between two instances of the delimiter are not considered to + * be part of the prompt length */ if (*cp && cp[1] == '\r') { delimiter = *cp; @@ -1594,20 +1599,20 @@ promptlen(const char *cp) return (dopprompt(cp, 0, false)); } -/* Read the variable part of a ${...} expression (ie, up to but not including - * the :[-+?=#%] or close-brace. +/* + * Read the variable part of a ${...} expression (i.e. up to but not + * including the :[-+?=#%] or close-brace). */ static char * get_brace_var(XString *wsp, char *wp) { + char c; enum parse_state { PS_INITIAL, PS_SAW_HASH, PS_IDENT, PS_NUMBER, PS_VAR1 - } state; - char c; + } state = PS_INITIAL; - state = PS_INITIAL; - while (1) { + while (/* CONSTCOND */ 1) { c = getsc(); /* State machine to figure out where the variable part ends. */ switch (state) { @@ -1622,7 +1627,19 @@ get_brace_var(XString *wsp, char *wp) state = PS_IDENT; else if (ksh_isdigit(c)) state = PS_NUMBER; - else if (ctype(c, C_VAR1)) + else if (c == '#') { + if (state == PS_SAW_HASH) { + char c2; + + c2 = getsc(); + ungetsc(c2); + if (c2 != '}') { + ungetsc(c); + goto out; + } + } + state = PS_VAR1; + } else if (ctype(c, C_VAR1)) state = PS_VAR1; else goto out; @@ -1640,7 +1657,8 @@ get_brace_var(XString *wsp, char *wp) *wp++ = *p++; } afree(tmp, ATEMP); - c = getsc(); /* the ] */ + /* the ] */ + c = getsc(); } goto out; } @@ -1656,7 +1674,8 @@ get_brace_var(XString *wsp, char *wp) *wp++ = c; } out: - *wp++ = '\0'; /* end of variable part */ + /* end of variable part */ + *wp++ = '\0'; ungetsc(c); return (wp); } @@ -1666,13 +1685,13 @@ get_brace_var(XString *wsp, char *wp) * if eof or newline was found. * (Returned string double null terminated) */ -static int +static bool arraysub(char **strp) { XString ws; - char *wp; - char c; - int depth = 1; /* we are just past the initial [ */ + char *wp, c; + /* we are just past the initial [ */ + int depth = 1; Xinit(ws, wp, 32, ATEMP); @@ -1689,18 +1708,30 @@ arraysub(char **strp) *wp++ = '\0'; *strp = Xclose(ws, wp); - return (depth == 0 ? 1 : 0); + return (tobool(depth == 0)); } /* Unget a char: handles case when we are already at the start of the buffer */ -static const char * +static void ungetsc(int c) { + struct sretrace_info *rp = retrace_info; + if (backslash_skip) backslash_skip--; - /* Don't unget eof... */ + /* Don't unget EOF... */ if (source->str == null && c == '\0') - return (source->str); + return; + while (rp) { + if (Xlength(rp->xs, rp->xp)) + rp->xp--; + rp = rp->next; + } + ungetsc_(c); +} +static void +ungetsc_(int c) +{ if (source->str > source->start) source->str--; else { @@ -1712,7 +1743,6 @@ ungetsc(int c) s->next = source; source = s; } - return (source->str); } @@ -1723,34 +1753,57 @@ getsc_bn(void) int c, c2; if (ignore_backslash_newline) - return (getsc_()); + return (o_getsc_u()); if (backslash_skip == 1) { backslash_skip = 2; - return (getsc_()); + return (o_getsc_u()); } backslash_skip = 0; - while (1) { - c = getsc_(); + while (/* CONSTCOND */ 1) { + c = o_getsc_u(); if (c == '\\') { - if ((c2 = getsc_()) == '\n') + if ((c2 = o_getsc_u()) == '\n') /* ignore the \newline; get the next char... */ continue; - ungetsc(c2); + ungetsc_(c2); backslash_skip = 1; } return (c); } } +void +yyskiputf8bom(void) +{ + int c; + + if ((unsigned char)(c = o_getsc_u()) != 0xEF) { + ungetsc_(c); + return; + } + if ((unsigned char)(c = o_getsc_u()) != 0xBB) { + ungetsc_(c); + ungetsc_(0xEF); + return; + } + if ((unsigned char)(c = o_getsc_u()) != 0xBF) { + ungetsc_(c); + ungetsc_(0xBB); + ungetsc_(0xEF); + return; + } + UTFMODE |= 8; +} + static Lex_state * push_state_(State_info *si, Lex_state *old_end) { - Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP); + Lex_state *news = alloc2(STATE_BSIZE, sizeof(Lex_state), ATEMP); - news[0].ls_info.base = old_end; + news[0].ls_base = old_end; si->base = &news[0]; si->end = &news[STATE_BSIZE]; return (&news[1]); @@ -1761,8 +1814,8 @@ pop_state_(State_info *si, Lex_state *old_end) { Lex_state *old_base = si->base; - si->base = old_end->ls_info.base - STATE_BSIZE; - si->end = old_end->ls_info.base; + si->base = old_end->ls_base - STATE_BSIZE; + si->end = old_end->ls_base; afree(old_base, ATEMP); @@ -1,10 +1,10 @@ -/* $OpenBSD: main.c,v 1.46 2010/05/19 17:36:08 jasper Exp $ */ +/* $OpenBSD: main.c,v 1.47 2011/09/07 11:33:25 otto Exp $ */ /* $OpenBSD: tty.c,v 1.9 2006/03/14 22:08:01 deraadt Exp $ */ /* $OpenBSD: io.c,v 1.22 2006/03/17 16:30:13 millert Exp $ */ /* $OpenBSD: table.c,v 1.13 2009/01/17 22:06:44 millert Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -23,7 +23,7 @@ * of said person's immediate fault when using the work as intended. */ -#define EXTERN +#define EXTERN #include "sh.h" #if HAVE_LANGINFO_CODESET @@ -33,15 +33,10 @@ #include <locale.h> #endif -__RCSID("$MirOS: src/bin/mksh/main.c,v 1.167 2010/07/04 17:45:15 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.200 2011/10/07 19:51:28 tg Exp $"); extern char **environ; -#if !HAVE_SETRESUGID -extern uid_t kshuid; -extern gid_t kshgid, kshegid; -#endif - #ifndef MKSHRC_PATH #define MKSHRC_PATH "~/.mkshrc" #endif @@ -50,10 +45,10 @@ extern gid_t kshgid, kshegid; #define MKSH_DEFAULT_TMPDIR "/tmp" #endif +void chvt_reinit(void); static void reclaim(void); static void remove_temps(struct temp *); -void chvt_reinit(void); -Source *mksh_init(int, const char *[]); +static mksh_uari_t rndsetup(void); #ifdef SIGWINCH static void x_sigwinch(int); #endif @@ -64,16 +59,19 @@ static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}"; static const char *initcoms[] = { - T_typeset, "-r", initvsn, NULL, - T_typeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL, - T_typeset, "-i10", "COLUMNS", "LINES", "OPTIND", "PGRP", "PPID", - "RANDOM", "SECONDS", "TMOUT", "USER_ID", NULL, - "alias", + Ttypeset, "-r", initvsn, NULL, + Ttypeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL, + Ttypeset, "-i10", "SECONDS", "TMOUT", NULL, + Talias, "integer=typeset -i", - T_local_typeset, - "hash=alias -t", /* not "alias -t --": hash -r needs to work */ + Tlocal_typeset, + /* not "alias -t --": hash -r needs to work */ + "hash=alias -t", "type=whence -v", -#ifndef MKSH_UNEMPLOYED +#if !defined(ANDROID) && !defined(MKSH_UNEMPLOYED) + /* not in Android for political reasons */ + /* not in ARGE mksh due to no job control */ + "stop=kill -STOP", "suspend=kill -STOP $$", #endif "autoload=typeset -fu", @@ -81,20 +79,68 @@ static const char *initcoms[] = { "history=fc -l", "nameref=typeset -n", "nohup=nohup ", - r_fc_e_, + Tr_fc_e_dash, "source=PATH=$PATH:. command .", "login=exec login", NULL, /* this is what AT&T ksh seems to track, with the addition of emacs */ - "alias", "-tU", + Talias, "-tU", "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls", "make", "mv", "pr", "rm", "sed", "sh", "vi", "who", NULL, NULL }; +static const char *restr_com[] = { + Ttypeset, "-r", "PATH", "ENV", "SHELL", NULL +}; + static int initio_done; -struct env *e = &kshstate_v.env_; +/* top-level parsing and execution environment */ +static struct env env; +struct env *e = &env; + +static mksh_uari_t +rndsetup(void) +{ + register uint32_t h; + struct { + ALLOC_ITEM alloc_INT; + void *dataptr, *stkptr, *mallocptr; + sigjmp_buf jbuf; + struct timeval tv; + struct timezone tz; + } *bufptr; + char *cp; + + cp = alloc(sizeof(*bufptr) - ALLOC_SIZE, APERM); +#ifdef DEBUG + /* clear the allocated space, for valgrind */ + memset(cp, 0, sizeof(*bufptr) - ALLOC_SIZE); +#endif + /* undo what alloc() did to the malloc result address */ + bufptr = (void *)(cp - ALLOC_SIZE); + /* PIE or something similar provides us with deltas here */ + bufptr->dataptr = &rndsetupstate; + /* ASLR in at least Windows, Linux, some BSDs */ + bufptr->stkptr = &bufptr; + /* randomised malloc in BSD (and possibly others) */ + bufptr->mallocptr = bufptr; + /* glibc pointer guard */ + sigsetjmp(bufptr->jbuf, 1); + /* introduce variation */ + gettimeofday(&bufptr->tv, &bufptr->tz); + + NZATInit(h); + /* variation through pid, ppid, and the works */ + NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate)); + /* some variation, some possibly entropy, depending on OE */ + NZATUpdateMem(h, bufptr, sizeof(*bufptr)); + NZAATFinish(h); + + afree(cp, APERM); + return ((mksh_uari_t)h); +} void chvt_reinit(void) @@ -105,19 +151,23 @@ chvt_reinit(void) kshppid = getppid(); } -Source * -mksh_init(int argc, const char *argv[]) +static const char *empty_argv[] = { + "mksh", NULL +}; + +int +main(int argc, const char *argv[]) { int argi, i; - Source *s; + Source *s = NULL; struct block *l; unsigned char restricted, errexit, utf_flag; - const char **wp; + char *cp; + const char *ccp, **wp; struct tbl *vp; struct stat s_stdin; #if !defined(_PATH_DEFPATH) && defined(_CS_PATH) - size_t k; - char *cp; + ssize_t k; #endif /* do things like getpgrp() et al. */ @@ -125,28 +175,59 @@ mksh_init(int argc, const char *argv[]) /* make sure argv[] is sane */ if (!*argv) { - static const char *empty_argv[] = { - "mksh", NULL - }; - argv = empty_argv; argc = 1; } - kshname = *argv; + kshname = argv[0]; - ainit(&aperm); /* initialise permanent Area */ + /* initialise permanent Area */ + ainit(&aperm); /* set up base environment */ - kshstate_v.env_.type = E_NONE; - ainit(&kshstate_v.env_.area); - newblock(); /* set up global l->vars and l->funs */ + env.type = E_NONE; + ainit(&env.area); + /* set up global l->vars and l->funs */ + newblock(); /* Do this first so output routines (eg, errorf, shellf) can work */ initio(); - argi = parse_args(argv, OF_FIRSTTIME, NULL); - if (argi < 0) - return (NULL); + /* determine the basename (without '-' or path) of the executable */ + ccp = kshname; + goto begin_parse_kshname; + while ((i = ccp[argi++])) { + if (i == '/') { + ccp += argi; + begin_parse_kshname: + argi = 0; + if (*ccp == '-') + ++ccp; + } + } + if (!*ccp) + ccp = empty_argv[0]; + + /* define built-in commands and see if we were called as one */ + ktinit(APERM, &builtins, + /* currently 50 builtins -> 80% of 64 (2^6) */ + 6); + for (i = 0; mkshbuiltins[i].name != NULL; i++) + if (!strcmp(ccp, builtin(mkshbuiltins[i].name, + mkshbuiltins[i].func))) + Flag(FAS_BUILTIN) = 1; + + if (!Flag(FAS_BUILTIN)) { + /* check for -T option early */ + argi = parse_args(argv, OF_FIRSTTIME, NULL); + if (argi < 0) + return (1); + +#ifdef MKSH_BINSHREDUCED + /* set FSH if we're called as -sh or /bin/sh or so */ + if (!strcmp(ccp, "sh")) + change_flag(FSH, OF_FIRSTTIME, 1); +#endif + } initvar(); @@ -157,28 +238,22 @@ mksh_init(int argc, const char *argv[]) coproc_init(); /* set up variable and command dictionaries */ - ktinit(&taliases, APERM, 0); - ktinit(&aliases, APERM, 0); + ktinit(APERM, &taliases, 0); + ktinit(APERM, &aliases, 0); #ifndef MKSH_NOPWNAM - ktinit(&homedirs, APERM, 0); + ktinit(APERM, &homedirs, 0); #endif /* define shell keywords */ initkeywords(); - /* define built-in commands */ - ktinit(&builtins, APERM, - /* must be 80% of 2^n (currently 44 builtins) */ 64); - for (i = 0; mkshbuiltins[i].name != NULL; i++) - builtin(mkshbuiltins[i].name, mkshbuiltins[i].func); - init_histvec(); #ifdef _PATH_DEFPATH def_path = _PATH_DEFPATH; #else #ifdef _CS_PATH - if ((k = confstr(_CS_PATH, NULL, 0)) != (size_t)-1 && k > 0 && + if ((k = confstr(_CS_PATH, NULL, 0)) > 0 && confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1) def_path = cp; else @@ -194,91 +269,74 @@ mksh_init(int argc, const char *argv[]) def_path = "/bin:/usr/bin:/sbin:/usr/sbin"; #endif - /* Set PATH to def_path (will set the path global variable). + /* + * Set PATH to def_path (will set the path global variable). * (import of environment below will probably change this setting). */ vp = global("PATH"); /* setstr can't fail here */ setstr(vp, def_path, KSH_RETURN_ERROR); - /* Turn on nohup by default for now - will change to off + /* + * Turn on nohup by default for now - will change to off * by default once people are aware of its existence * (AT&T ksh does not have a nohup option - it always sends * the hup). */ Flag(FNOHUP) = 1; - /* Turn on brace expansion by default. AT&T kshs that have + /* + * Turn on brace expansion by default. AT&T kshs that have * alternation always have it on. */ Flag(FBRACEEXPAND) = 1; - /* Set edit mode to emacs by default, may be overridden + /* + * Set edit mode to emacs by default, may be overridden * by the environment or the user. Also, we want tab completion - * on in vi by default. */ + * on in vi by default. + */ change_flag(FEMACS, OF_SPECIAL, 1); #if !MKSH_S_NOVI Flag(FVITABCOMPLETE) = 1; #endif -#ifdef MKSH_BINSHREDUCED - /* set FSH if we're called as -sh or /bin/sh or so */ - { - const char *cc; - - cc = kshname; - i = 0; argi = 0; - while (cc[i] != '\0') - /* the following line matches '-' and '/' ;-) */ - if ((cc[i++] | 2) == '/') - argi = i; - if (((cc[argi] | 0x20) == 's') && ((cc[argi + 1] | 0x20) == 'h')) - change_flag(FSH, OF_FIRSTTIME, 1); - } -#endif - /* import environment */ if (environ != NULL) for (wp = (const char **)environ; *wp != NULL; wp++) typeset(*wp, IMPORT | EXPORT, 0, 0, 0); - typeset(initifs, 0, 0, 0, 0); /* for security */ + /* for security */ + typeset(initifs, 0, 0, 0, 0); /* assign default shell variable values */ substitute(initsubs, 0); /* Figure out the current working directory and set $PWD */ - { - struct stat s_pwd, s_dot; - struct tbl *pwd_v = global("PWD"); - char *pwd = str_val(pwd_v); - char *pwdx = pwd; - - /* Try to use existing $PWD if it is valid */ - if (pwd[0] != '/' || - stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 || - s_pwd.st_dev != s_dot.st_dev || - s_pwd.st_ino != s_dot.st_ino) - pwdx = NULL; - set_current_wd(pwdx); - if (current_wd[0]) - simplify_path(current_wd); - /* Only set pwd if we know where we are or if it had a - * bogus value - */ - if (current_wd[0] || pwd != null) - /* setstr can't fail here */ - setstr(pwd_v, current_wd, KSH_RETURN_ERROR); - } + vp = global("PWD"); + cp = str_val(vp); + /* Try to use existing $PWD if it is valid */ + set_current_wd((cp[0] == '/' && test_eval(NULL, TO_FILEQ, cp, ".", + true)) ? cp : NULL); + if (current_wd[0]) + simplify_path(current_wd); + /* Only set pwd if we know where we are or if it had a bogus value */ + if (current_wd[0] || *cp) + /* setstr can't fail here */ + setstr(vp, current_wd, KSH_RETURN_ERROR); for (wp = initcoms; *wp != NULL; wp++) { shcomexec(wp); while (*wp != NULL) wp++; } - setint(global("COLUMNS"), 0); - setint(global("LINES"), 0); - setint(global("OPTIND"), 1); + setint_n(global("COLUMNS"), 0); + setint_n(global("LINES"), 0); + setint_n(global("OPTIND"), 1); + + kshuid = getuid(); + kshgid = getgid(); + kshegid = getegid(); safe_prompt = ksheuid ? "$ " : "# "; vp = global("PS1"); @@ -287,22 +345,24 @@ mksh_init(int argc, const char *argv[]) (!ksheuid && !strchr(str_val(vp), '#'))) /* setstr can't fail here */ setstr(vp, safe_prompt, KSH_RETURN_ERROR); - setint((vp = global("PGRP")), (mksh_uari_t)kshpgrp); + setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp); vp->flag |= INT_U; - setint((vp = global("PPID")), (mksh_uari_t)kshppid); + setint_n((vp = global("PPID")), (mksh_uari_t)kshppid); vp->flag |= INT_U; - setint((vp = global("RANDOM")), (mksh_uari_t)evilhash(kshname)); + setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid); vp->flag |= INT_U; - setint((vp = global("USER_ID")), (mksh_uari_t)ksheuid); + setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid); vp->flag |= INT_U; + setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid); + vp->flag |= INT_U; + setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid); + vp->flag |= INT_U; + setint_n((vp = global("RANDOM")), rndsetup()); + vp->flag |= INT_U; + setint_n((vp_pipest = global("PIPESTATUS")), 0); /* Set this before parsing arguments */ -#if HAVE_SETRESUGID - Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid(); -#else - Flag(FPRIVILEGED) = (kshuid = getuid()) != ksheuid || - (kshgid = getgid()) != (kshegid = getegid()); -#endif + Flag(FPRIVILEGED) = kshuid != ksheuid || kshgid != kshegid; /* this to note if monitor is set on command line (see below) */ #ifndef MKSH_UNEMPLOYED @@ -311,18 +371,61 @@ mksh_init(int argc, const char *argv[]) /* this to note if utf-8 mode is set on command line (see below) */ UTFMODE = 2; - argi = parse_args(argv, OF_CMDLINE, NULL); - if (argi < 0) - return (NULL); + if (!Flag(FAS_BUILTIN)) { + argi = parse_args(argv, OF_CMDLINE, NULL); + if (argi < 0) + return (1); + } + +#ifdef DEBUG + /* test wraparound of arithmetic types */ + { + volatile long xl; + volatile unsigned long xul; + volatile int xi; + volatile unsigned int xui; + volatile mksh_ari_t xa; + volatile mksh_uari_t xua, xua2; + volatile uint8_t xc; + + xa = 2147483647; + xua = 2147483647; + ++xa; + ++xua; + xua2 = xa; + xl = xa; + xul = xua; + xa = 0; + xua = 0; + --xa; + --xua; + xi = xa; + xui = xua; + xa = -1; + xua = xa; + ++xa; + ++xua; + xc = 0; + --xc; + if ((xua2 != 2147483648UL) || + (xl != -2147483648L) || (xul != 2147483648UL) || + (xi != -1) || (xui != 4294967295U) || + (xa != 0) || (xua != 0) || (xc != 255)) + errorf("integer wraparound test failed"); + } +#endif /* process this later only, default to off (hysterical raisins) */ utf_flag = UTFMODE; UTFMODE = 0; - if (Flag(FCOMMAND)) { + if (Flag(FAS_BUILTIN)) { + /* auto-detect from environment variables, always */ + utf_flag = 3; + } else if (Flag(FCOMMAND)) { s = pushs(SSTRING, ATEMP); if (!(s->start = s->str = argv[argi++])) - errorf("-c requires an argument"); + errorf("%s %s", "-c", "requires an argument"); #ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */ if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--")) @@ -336,7 +439,7 @@ mksh_init(int argc, const char *argv[]) s->u.shf = shf_open(s->file, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); if (s->u.shf == NULL) { - shl_stdout_ok = 0; + shl_stdout_ok = false; warningf(true, "%s: %s", s->file, strerror(errno)); /* mandated by SUSv4 */ exstat = 127; @@ -365,37 +468,17 @@ mksh_init(int argc, const char *argv[]) /* initialise job control */ j_init(); - /* set: 0/1; unset: 2->0 */ - UTFMODE = utf_flag & 1; /* Do this after j_init(), as tty_fd is not initialised until then */ if (Flag(FTALKING)) { if (utf_flag == 2) { #ifndef MKSH_ASSUME_UTF8 -#define isuc(x) (((x) != NULL) && \ - (stristr((x), "UTF-8") || stristr((x), "utf8"))) - /* Check if we're in a UTF-8 locale */ - const char *ccp; - -#if HAVE_SETLOCALE_CTYPE - ccp = setlocale(LC_CTYPE, ""); -#if HAVE_LANGINFO_CODESET - if (!isuc(ccp)) - ccp = nl_langinfo(CODESET); -#endif -#else - /* these were imported from environ earlier */ - ccp = str_val(global("LC_ALL")); - if (ccp == null) - ccp = str_val(global("LC_CTYPE")); - if (ccp == null) - ccp = str_val(global("LANG")); -#endif - UTFMODE = isuc(ccp); -#undef isuc + /* auto-detect from locale or environment */ + utf_flag = 4; #elif MKSH_ASSUME_UTF8 - UTFMODE = 1; + utf_flag = 1; #else - UTFMODE = 0; + /* always disable UTF-8 (for interactive) */ + utf_flag = 0; #endif } x_init(); @@ -408,10 +491,62 @@ mksh_init(int argc, const char *argv[]) #endif l = e->loc; - l->argv = &argv[argi - 1]; - l->argc = argc - argi; - l->argv[0] = kshname; - getopts_reset(1); + if (Flag(FAS_BUILTIN)) { + l->argc = argc; + l->argv = argv; + l->argv[0] = ccp; + } else { + l->argc = argc - argi; + l->argv = &argv[argi - 1]; + l->argv[0] = kshname; + getopts_reset(1); + } + + /* divine the initial state of the utf8-mode Flag */ +#define isuc(x) (((x) != NULL) && \ + (stristr((x), "UTF-8") || stristr((x), "utf8"))) + ccp = null; + switch (utf_flag) { + + /* auto-detect from locale or environment */ + case 4: +#if HAVE_SETLOCALE_CTYPE + ccp = setlocale(LC_CTYPE, ""); +#if HAVE_LANGINFO_CODESET + if (!isuc(ccp)) + ccp = nl_langinfo(CODESET); +#endif + if (!isuc(ccp)) + ccp = null; + /* FALLTHROUGH */ +#endif + + /* auto-detect from environment */ + case 3: + /* these were imported from environ earlier */ + if (ccp == null) + ccp = str_val(global("LC_ALL")); + if (ccp == null) + ccp = str_val(global("LC_CTYPE")); + if (ccp == null) + ccp = str_val(global("LANG")); + UTFMODE = isuc(ccp); + break; + + /* not set on command line, not FTALKING */ + case 2: + /* unknown values */ + default: + utf_flag = 0; + /* FALLTHROUGH */ + + /* known values */ + case 1: + case 0: + UTFMODE = utf_flag; + break; + } +#undef isuc /* Disable during .profile/ENV reading */ restricted = Flag(FRESTRICTED); @@ -419,20 +554,21 @@ mksh_init(int argc, const char *argv[]) errexit = Flag(FERREXIT); Flag(FERREXIT) = 0; - /* Do this before profile/$ENV so that if it causes problems in them, + /* + * Do this before profile/$ENV so that if it causes problems in them, * user will know why things broke. */ if (!current_wd[0] && Flag(FTALKING)) - warningf(false, "Cannot determine current working directory"); + warningf(false, "can't determine current directory"); if (Flag(FLOGIN)) { - include(KSH_SYSTEM_PROFILE, 0, NULL, 1); + include(MKSH_SYSTEM_PROFILE, 0, NULL, 1); if (!Flag(FPRIVILEGED)) include(substitute("$HOME/.profile", 0), 0, NULL, 1); } if (Flag(FPRIVILEGED)) - include("/etc/suid_profile", 0, NULL, 1); + include(MKSH_SUID_PROFILE, 0, NULL, 1); else if (Flag(FTALKING)) { char *env_file; @@ -444,40 +580,27 @@ mksh_init(int argc, const char *argv[]) } if (restricted) { - static const char *restr_com[] = { - T_typeset, "-r", "PATH", - "ENV", "SHELL", - NULL - }; shcomexec(restr_com); /* After typeset command... */ Flag(FRESTRICTED) = 1; } Flag(FERREXIT) = errexit; - if (Flag(FTALKING)) { + if (Flag(FTALKING)) hist_init(s); - alarm_init(); - } else - Flag(FTRACKALL) = 1; /* set after ENV */ - - return (s); -} + else + /* set after ENV */ + Flag(FTRACKALL) = 1; -int -main(int argc, const char *argv[]) -{ - Source *s; + alarm_init(); - kshstate_v.lcg_state_ = 5381; + if (Flag(FAS_BUILTIN)) + return (shcomexec(l->argv)); - if ((s = mksh_init(argc, argv))) { - /* put more entropy into the LCG */ - change_random(s, sizeof(*s)); - /* doesn’t return */ - shell(s, true); - } - return (1); + /* doesn't return */ + shell(s, true); + /* NOTREACHED */ + return (0); } int @@ -511,9 +634,11 @@ include(const char *name, int argc, const char **argv, int intr_ok) switch (i) { case LRETURN: case LERROR: - return (exstat & 0xff); /* see below */ + /* see below */ + return (exstat & 0xFF); case LINTR: - /* intr_ok is set if we are including .profile or $ENV. + /* + * intr_ok is set if we are including .profile or $ENV. * If user ^Cs out, we don't want to kill the shell... */ if (intr_ok && (exstat - 128) != SIGTERM) @@ -525,7 +650,7 @@ include(const char *name, int argc, const char **argv, int intr_ok) unwind(i); /* NOTREACHED */ default: - internal_errorf("include: %d", i); + internal_errorf("%s %d", "include", i); /* NOTREACHED */ } } @@ -542,7 +667,8 @@ include(const char *name, int argc, const char **argv, int intr_ok) e->loc->argv = old_argv; e->loc->argc = old_argc; } - return (i & 0xff); /* & 0xff to ensure value not -1 */ + /* & 0xff to ensure value not -1 */ + return (i & 0xFF); } /* spawn a command into a shell optionally keeping track of the line number */ @@ -567,30 +693,32 @@ shell(Source * volatile s, volatile int toplevel) volatile int wastty = s->flags & SF_TTY; volatile int attempts = 13; volatile int interactive = Flag(FTALKING) && toplevel; + volatile bool sfirst = true; Source *volatile old_source = source; int i; - s->flags |= SF_FIRST; /* enable UTF-8 BOM check */ - newenv(E_PARSE); if (interactive) really_exit = 0; i = sigsetjmp(e->jbuf, 0); if (i) { switch (i) { - case LINTR: /* we get here if SIGINT not caught or ignored */ + case LINTR: + /* we get here if SIGINT not caught or ignored */ case LERROR: case LSHELL: if (interactive) { if (i == LINTR) shellf("\n"); - /* Reset any eof that was read as part of a + /* + * Reset any eof that was read as part of a * multiline command. */ if (Flag(FIGNOREEOF) && s->type == SEOF && wastty) s->type = SSTDIN; - /* Used by exit command to get back to + /* + * Used by exit command to get back to * top level shell. Kind of strange since * interactive is set if we are reading from * a tty, but to have stopped jobs, one only @@ -606,16 +734,17 @@ shell(Source * volatile s, volatile int toplevel) case LRETURN: source = old_source; quitenv(NULL); - unwind(i); /* keep on going */ + /* keep on going */ + unwind(i); /* NOTREACHED */ default: source = old_source; quitenv(NULL); - internal_errorf("shell: %d", i); + internal_errorf("%s %d", "shell", i); /* NOTREACHED */ } } - while (1) { + while (/* CONSTCOND */ 1) { if (trap) runtraps(0); @@ -629,17 +758,19 @@ shell(Source * volatile s, volatile int toplevel) j_notify(); set_prompt(PS1, s); } - t = compile(s); + t = compile(s, sfirst); + sfirst = false; if (t != NULL && t->type == TEOF) { if (wastty && Flag(FIGNOREEOF) && --attempts > 0) { - shellf("Use 'exit' to leave ksh\n"); + shellf("Use 'exit' to leave mksh\n"); s->type = SSTDIN; } else if (wastty && !really_exit && j_stopped_running()) { really_exit = 1; s->type = SSTDIN; } else { - /* this for POSIX which says EXIT traps + /* + * this for POSIX which says EXIT traps * shall be taken in the environment * immediately after the last command * executed. @@ -668,14 +799,18 @@ unwind(int i) { /* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */ if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) && - sigtraps[SIGEXIT_].trap)) { - runtrap(&sigtraps[SIGEXIT_]); + sigtraps[ksh_SIGEXIT].trap)) { + ++trap_nested; + runtrap(&sigtraps[ksh_SIGEXIT], trap_nested == 1); + --trap_nested; i = LLEAVE; } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) { - runtrap(&sigtraps[SIGERR_]); + ++trap_nested; + runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1); + --trap_nested; i = LLEAVE; } - while (1) { + while (/* CONSTCOND */ 1) { switch (e->type) { case E_PARSE: case E_FUNC: @@ -705,7 +840,8 @@ newenv(int type) * so first get the actually used memory, then assign it */ cp = alloc(sizeof(struct env) - ALLOC_SIZE, ATEMP); - ep = (void *)(cp - ALLOC_SIZE); /* undo what alloc() did */ + /* undo what alloc() did to the malloc result address */ + ep = (void *)(cp - ALLOC_SIZE); /* initialise public members of struct env (not the ALLOC_ITEM) */ ainit(&ep->area); ep->oenv = e; @@ -732,14 +868,17 @@ quitenv(struct shf *shf) /* if ep->savefd[fd] < 0, means fd was closed */ if (ep->savefd[fd]) restfd(fd, ep->savefd[fd]); - if (ep->savefd[2]) /* Clear any write errors */ + if (ep->savefd[2]) + /* Clear any write errors */ shf_reopen(2, SHF_WR, shl_out); } - /* Bottom of the stack. + /* + * Bottom of the stack. * Either main shell is exiting or cleanup_parents_env() was called. */ if (ep->oenv == NULL) { - if (ep->type == E_NONE) { /* Main shell exiting? */ + if (ep->type == E_NONE) { + /* Main shell exiting? */ #if HAVE_PERSISTENT_HISTORY if (Flag(FTALKING)) hist_finish(); @@ -748,7 +887,8 @@ quitenv(struct shf *shf) if (ep->flags & EF_FAKE_SIGDIE) { int sig = exstat - 128; - /* ham up our death a bit (AT&T ksh + /* + * ham up our death a bit (AT&T ksh * only seems to do this for SIGTERM) * Don't do it for SIGQUIT, since we'd * dump a core.. @@ -832,7 +972,8 @@ remove_temps(struct temp *tp) unlink(tp->name); } -/* Initialise tty_fd. Used for saving/reseting tty modes upon +/* + * Initialise tty_fd. Used for saving/reseting tty modes upon * foreground job completion and for setting up tty process group. */ void @@ -845,19 +986,21 @@ tty_init(bool init_ttystate, bool need_tty) close(tty_fd); tty_fd = -1; } - tty_devtty = 1; + tty_devtty = true; #ifdef _UWIN - /* XXX imake style */ - if (isatty(3)) + /*XXX imake style */ + if (isatty(3)) { + /* fd 3 on UWIN _is_ /dev/tty (or our controlling tty) */ tfd = 3; - else + do_close = false; + } else #endif - if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { - tty_devtty = 0; + if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { + tty_devtty = false; if (need_tty) - warningf(false, - "No controlling tty (open /dev/tty: %s)", + warningf(false, "%s: %s %s: %s", + "No controlling tty", "open", "/dev/tty", strerror(errno)); } if (tfd < 0) { @@ -868,20 +1011,18 @@ tty_init(bool init_ttystate, bool need_tty) tfd = 2; else { if (need_tty) - warningf(false, - "Can't find tty file descriptor"); + warningf(false, "can't find tty fd"); return; } } if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) { if (need_tty) - warningf(false, "j_ttyinit: dup of tty fd failed: %s", - strerror(errno)); + warningf(false, "%s: %s %s: %s", "j_ttyinit", + "dup of tty fd", "failed", strerror(errno)); } else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) { if (need_tty) - warningf(false, - "j_ttyinit: can't set close-on-exec flag: %s", - strerror(errno)); + warningf(false, "%s: %s: %s", "j_ttyinit", + "can't set close-on-exec flag", strerror(errno)); close(tty_fd); tty_fd = -1; } else if (init_ttystate) @@ -900,21 +1041,62 @@ tty_close(void) } /* A shell error occurred (eg, syntax error, etc.) */ + +#define VWARNINGF_ERRORPREFIX 1 +#define VWARNINGF_FILELINE 2 +#define VWARNINGF_BUILTIN 4 +#define VWARNINGF_INTERNAL 8 + +static void vwarningf(unsigned int, const char *, va_list) + MKSH_A_FORMAT(__printf__, 2, 0); + +static void +vwarningf(unsigned int flags, const char *fmt, va_list ap) +{ + if (*fmt != 1) { + if (flags & VWARNINGF_INTERNAL) + shf_fprintf(shl_out, "internal error: "); + if (flags & VWARNINGF_ERRORPREFIX) + error_prefix(tobool(flags & VWARNINGF_FILELINE)); + if ((flags & VWARNINGF_BUILTIN) && + /* not set when main() calls parse_args() */ + builtin_argv0 && builtin_argv0 != kshname) + shf_fprintf(shl_out, "%s: ", builtin_argv0); + shf_vfprintf(shl_out, fmt, ap); + shf_putchar('\n', shl_out); + } + shf_flush(shl_out); +} + +void +errorfx(int rc, const char *fmt, ...) +{ + va_list va; + + exstat = rc; + + /* debugging: note that stdout not valid */ + shl_stdout_ok = false; + + va_start(va, fmt); + vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va); + va_end(va); + unwind(LERROR); +} + void errorf(const char *fmt, ...) { va_list va; - shl_stdout_ok = 0; /* debugging: note that stdout not valid */ exstat = 1; - if (*fmt != 1) { - error_prefix(true); - va_start(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - } - shf_flush(shl_out); + + /* debugging: note that stdout not valid */ + shl_stdout_ok = false; + + va_start(va, fmt); + vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va); + va_end(va); unwind(LERROR); } @@ -924,15 +1106,14 @@ warningf(bool fileline, const char *fmt, ...) { va_list va; - error_prefix(fileline); va_start(va, fmt); - shf_vfprintf(shl_out, fmt, va); + vwarningf(VWARNINGF_ERRORPREFIX | (fileline ? VWARNINGF_FILELINE : 0), + fmt, va); va_end(va); - shf_putchar('\n', shl_out); - shf_flush(shl_out); } -/* Used by built-in utilities to prefix shell and utility name to message +/* + * Used by built-in utilities to prefix shell and utility name to message * (also unwinds environments for special builtins). */ void @@ -940,20 +1121,18 @@ bi_errorf(const char *fmt, ...) { va_list va; - shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + /* debugging: note that stdout not valid */ + shl_stdout_ok = false; + exstat = 1; - if (*fmt != 1) { - error_prefix(true); - /* not set when main() calls parse_args() */ - if (builtin_argv0) - shf_fprintf(shl_out, "%s: ", builtin_argv0); - va_start(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - } - shf_flush(shl_out); - /* POSIX special builtins and ksh special builtins cause + + va_start(va, fmt); + vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE | + VWARNINGF_BUILTIN, fmt, va); + va_end(va); + + /* + * POSIX special builtins and ksh special builtins cause * non-interactive shells to exit. * XXX odd use of KEEPASN; also may not want LERROR here */ @@ -965,21 +1144,12 @@ bi_errorf(const char *fmt, ...) /* Called when something that shouldn't happen does */ void -internal_verrorf(const char *fmt, va_list ap) -{ - shf_fprintf(shl_out, "internal error: "); - shf_vfprintf(shl_out, fmt, ap); - shf_putchar('\n', shl_out); - shf_flush(shl_out); -} - -void internal_errorf(const char *fmt, ...) { va_list va; va_start(va, fmt); - internal_verrorf(fmt, va); + vwarningf(VWARNINGF_INTERNAL, fmt, va); va_end(va); unwind(LERROR); } @@ -990,7 +1160,7 @@ internal_warningf(const char *fmt, ...) va_list va; va_start(va, fmt); - internal_verrorf(fmt, va); + vwarningf(VWARNINGF_INTERNAL, fmt, va); va_end(va); } @@ -1015,7 +1185,8 @@ shellf(const char *fmt, ...) { va_list va; - if (!initio_done) /* shl_out may not be set up yet... */ + if (!initio_done) + /* shl_out may not be set up yet... */ return; va_start(va, fmt); shf_vfprintf(shl_out, fmt, va); @@ -1051,9 +1222,11 @@ struct shf shf_iob[3]; void initio(void) { - shf_fdopen(1, SHF_WR, shl_stdout); /* force buffer allocation */ + /* force buffer allocation */ + shf_fdopen(1, SHF_WR, shl_stdout); shf_fdopen(2, SHF_WR, shl_out); - shf_fdopen(2, SHF_WR, shl_spare); /* force buffer allocation */ + /* force buffer allocation */ + shf_fdopen(2, SHF_WR, shl_spare); initio_done = 1; } @@ -1067,7 +1240,7 @@ ksh_dup2(int ofd, int nfd, bool errok) errorf("too many files open in shell"); #ifdef __ultrix - /* XXX imake style */ + /*XXX imake style */ if (rv >= 0) fcntl(nfd, F_SETFD, 0); #endif @@ -1076,8 +1249,8 @@ ksh_dup2(int ofd, int nfd, bool errok) } /* - * move fd from user space (0<=fd<10) to shell space (fd>=10), - * set close-on-exec flag. + * Move fd from user space (0 <= fd < 10) to shell space (fd >= 10), + * set close-on-exec flag. See FDBASE in sh.h, maybe 24 not 10 here. */ short savefd(int fd) @@ -1098,10 +1271,12 @@ restfd(int fd, int ofd) { if (fd == 2) shf_flush(&shf_iob[fd]); - if (ofd < 0) /* original fd closed */ + if (ofd < 0) + /* original fd closed */ close(fd); else if (fd != ofd) { - ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */ + /*XXX: what to do if this dup fails? */ + ksh_dup2(ofd, fd, true); close(ofd); } } @@ -1128,7 +1303,8 @@ closepipe(int *pv) close(pv[1]); } -/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn +/* + * Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor. */ int @@ -1151,7 +1327,8 @@ check_fd(const char *name, int mode, const char **emsgp) return (-1); } fl &= O_ACCMODE; - /* X_OK is a kludge to disable this check for dups (x<&1): + /* + * X_OK is a kludge to disable this check for dups (x<&1): * historical shells never did this check (XXX don't know what * POSIX has to say). */ @@ -1187,7 +1364,8 @@ coproc_read_close(int fd) } } -/* Called by c_read() and by iosetup() to close the other side of the +/* + * Called by c_read() and by iosetup() to close the other side of the * read pipe, so reads will actually terminate. */ void @@ -1199,7 +1377,8 @@ coproc_readw_close(int fd) } } -/* Called by c_print when a write to a fd fails with EPIPE and by iosetup +/* + * Called by c_print when a write to a fd fails with EPIPE and by iosetup * when co-process input is dup'd */ void @@ -1211,7 +1390,8 @@ coproc_write_close(int fd) } } -/* Called to check for existence of/value of the co-process file descriptor. +/* + * Called to check for existence of/value of the co-process file descriptor. * (Used by check_fd() and by c_read/c_print to deal with -p option). */ int @@ -1226,7 +1406,8 @@ coproc_getfd(int mode, const char **emsgp) return (-1); } -/* called to close file descriptors related to the coprocess (if any) +/* + * called to close file descriptors related to the coprocess (if any) * Should be called with SIGCHLD blocked. */ void @@ -1253,7 +1434,7 @@ struct temp * maketemp(Area *ap, Temp_type type, struct temp **tlist) { struct temp *tp; - int len; + size_t len; int fd; char *pathname; const char *dir; @@ -1265,6 +1446,7 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist) pathname = tempnam(dir, "mksh."); len = ((pathname == NULL) ? 0 : strlen(pathname)) + 1; #endif + /* reasonably sure that this will not overflow */ tp = alloc(sizeof(struct temp) + len, ap); tp->name = (char *)&tp[1]; #if !HAVE_MKSTEMP @@ -1272,14 +1454,14 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist) tp->name[0] = '\0'; else { memcpy(tp->name, pathname, len); - free(pathname); + free_ostempnam(pathname); } #endif pathname = tp->name; tp->shf = NULL; tp->type = type; #if HAVE_MKSTEMP - shf_snprintf(pathname, len, "%s/mksh.XXXXXXXXXX", dir); + shf_snprintf(pathname, len, "%s%s", dir, "/mksh.XXXXXXXXXX"); if ((fd = mkstemp(pathname)) >= 0) #else if (tp->name[0] && (fd = open(tp->name, O_CREAT | O_RDWR, 0600)) >= 0) @@ -1297,41 +1479,48 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist) * but with a slightly tweaked implementation written from scratch. */ -#define INIT_TBLS 8 /* initial table size (power of 2) */ +#define INIT_TBLSHIFT 3 /* initial table shift (2^3 = 8) */ #define PERTURB_SHIFT 5 /* see Python 2.5.4 Objects/dictobject.c */ -static void texpand(struct table *, size_t); +static void tgrow(struct table *); static int tnamecmp(const void *, const void *); -static struct tbl *ktscan(struct table *, const char *, uint32_t, - struct tbl ***); static void -texpand(struct table *tp, size_t nsize) +tgrow(struct table *tp) { - size_t i, j, osize = tp->size, perturb; + size_t i, j, osize, mask, perturb; struct tbl *tblp, **pp; struct tbl **ntblp, **otblp = tp->tbls; - ntblp = alloc(nsize * sizeof(struct tbl *), tp->areap); - for (i = 0; i < nsize; i++) - ntblp[i] = NULL; - tp->size = nsize; - tp->nfree = (nsize * 4) / 5; /* table can get 80% full */ + if (tp->tshift > 29) + internal_errorf("hash table size limit reached"); + + /* calculate old size, new shift and new size */ + osize = (size_t)1 << (tp->tshift++); + i = osize << 1; + + ntblp = alloc2(i, sizeof(struct tbl *), tp->areap); + /* multiplication cannot overflow: alloc2 checked that */ + memset(ntblp, 0, i * sizeof(struct tbl *)); + + /* table can get 80% full except when reaching its limit */ + tp->nfree = (tp->tshift == 30) ? 0x3FFF0000UL : ((i * 4) / 5); tp->tbls = ntblp; if (otblp == NULL) return; - nsize--; /* from here on nsize := mask */ + + mask = i - 1; for (i = 0; i < osize; i++) if ((tblp = otblp[i]) != NULL) { if ((tblp->flag & DEFINED)) { /* search for free hash table slot */ - j = (perturb = tblp->ua.hval) & nsize; + j = (perturb = tblp->ua.hval) & mask; goto find_first_empty_slot; find_next_empty_slot: j = (j << 2) + j + perturb + 1; perturb >>= PERTURB_SHIFT; find_first_empty_slot: - pp = &ntblp[j & nsize]; + pp = &ntblp[j & mask]; if (*pp != NULL) goto find_next_empty_slot; /* found an empty hash table slot */ @@ -1345,23 +1534,23 @@ texpand(struct table *tp, size_t nsize) } void -ktinit(struct table *tp, Area *ap, size_t tsize) +ktinit(Area *ap, struct table *tp, uint8_t initshift) { tp->areap = ap; tp->tbls = NULL; - tp->size = tp->nfree = 0; - if (tsize) - texpand(tp, tsize); + tp->tshift = ((initshift > INIT_TBLSHIFT) ? + initshift : INIT_TBLSHIFT) - 1; + tgrow(tp); } /* table, name (key) to search for, hash(name), rv pointer to tbl ptr */ -static struct tbl * +struct tbl * ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp) { size_t j, perturb, mask; struct tbl **pp, *p; - mask = tp->size - 1; + mask = ((size_t)1 << (tp->tshift)) - 1; /* search for hash table slot matching name */ j = (perturb = h) & mask; goto find_first_slot; @@ -1379,35 +1568,27 @@ ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp) return (p); } -/* table, name (key) to search for, hash(n) */ -struct tbl * -ktsearch(struct table *tp, const char *n, uint32_t h) -{ - return (tp->size ? ktscan(tp, n, h, NULL) : NULL); -} - /* table, name (key) to enter, hash(n) */ struct tbl * ktenter(struct table *tp, const char *n, uint32_t h) { struct tbl **pp, *p; - int len; + size_t len; - if (tp->size == 0) - texpand(tp, INIT_TBLS); Search: if ((p = ktscan(tp, n, h, &pp))) return (p); - if (tp->nfree <= 0) { + if (tp->nfree == 0) { /* too full */ - texpand(tp, 2 * tp->size); + tgrow(tp); goto Search; } /* create new tbl entry */ - len = strlen(n) + 1; - p = alloc(offsetof(struct tbl, name[0]) + len, tp->areap); + len = strlen(n); + checkoktoadd(len, offsetof(struct tbl, name[0]) + 1); + p = alloc(offsetof(struct tbl, name[0]) + ++len, tp->areap); p->flag = 0; p->type = 0; p->areap = tp->areap; @@ -1425,7 +1606,7 @@ ktenter(struct table *tp, const char *n, uint32_t h) void ktwalk(struct tstate *ts, struct table *tp) { - ts->left = tp->size; + ts->left = (size_t)1 << (tp->tshift); ts->next = tp->tbls; } @@ -1455,15 +1636,19 @@ ktsort(struct table *tp) size_t i; struct tbl **p, **sp, **dp; - p = alloc((tp->size + 1) * sizeof(struct tbl *), ATEMP); + /* + * since the table is never entirely full, no need to reserve + * additional space for the trailing NULL appended below + */ + i = (size_t)1 << (tp->tshift); + p = alloc2(i, sizeof(struct tbl *), ATEMP); sp = tp->tbls; /* source */ dp = p; /* dest */ - i = (size_t)tp->size; while (i--) if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) || ((*dp)->flag & ARRAY))) dp++; - qsort(p, (i = dp - p), sizeof(void *), tnamecmp); + qsort(p, (i = dp - p), sizeof(struct tbl *), tnamecmp); p[i] = NULL; return (p); } @@ -2,7 +2,7 @@ /* $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -29,15 +29,13 @@ #include <grp.h> #endif -__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.141 2010/07/17 22:09:36 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.172 2011/09/07 15:24:18 tg Exp $"); -unsigned char chtypes[UCHAR_MAX + 1]; /* type bits for unsigned char */ - -#if !HAVE_SETRESUGID -uid_t kshuid; -gid_t kshgid, kshegid; -#endif +/* type bits for unsigned char */ +unsigned char chtypes[UCHAR_MAX + 1]; +static const unsigned char *pat_scan(const unsigned char *, + const unsigned char *, bool); static int do_gmatch(const unsigned char *, const unsigned char *, const unsigned char *, const unsigned char *); static const unsigned char *cclass(const unsigned char *, int); @@ -45,6 +43,20 @@ static const unsigned char *cclass(const unsigned char *, int); static void chvt(const char *); #endif +/*XXX this should go away */ +static int make_path(const char *, const char *, char **, XString *, int *); + +#ifdef SETUID_CAN_FAIL_WITH_EAGAIN +/* we don't need to check for other codes, EPERM won't happen */ +#define DO_SETUID(func, argvec) do { \ + if ((func argvec) && errno == EAGAIN) \ + errorf("%s failed with EAGAIN, probably due to a" \ + " too low process limit; aborting", #func); \ +} while (/* CONSTCOND */ 0) +#else +#define DO_SETUID(func, argvec) func argvec +#endif + /* * Fast character classes */ @@ -56,7 +68,8 @@ setctypes(const char *s, int t) if (t & C_IFS) { for (i = 0; i < UCHAR_MAX + 1; i++) chtypes[i] &= ~C_IFS; - chtypes[0] |= C_IFS; /* include \0 in C_IFS */ + /* include \0 in C_IFS */ + chtypes[0] |= C_IFS; } while (*s != 0) chtypes[(unsigned char)*s++] |= t; @@ -73,7 +86,8 @@ initctypes(void) chtypes[c] |= C_ALPHA; chtypes['_'] |= C_ALPHA; setctypes("0123456789", C_DIGIT); - setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */ + /* \0 added automatically */ + setctypes(" \t\n|&;<>()", C_LEX1); setctypes("*@#!$-?", C_VAR1); setctypes(" \t\n", C_IFSWS); setctypes("=-+?", C_SUBOP1); @@ -82,12 +96,15 @@ initctypes(void) /* called from XcheckN() to grow buffer */ char * -Xcheck_grow_(XString *xsp, const char *xp, unsigned int more) +Xcheck_grow_(XString *xsp, const char *xp, size_t more) { const char *old_beg = xsp->beg; - xsp->len += more > xsp->len ? more : xsp->len; - xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap); + if (more < xsp->len) + more = xsp->len; + /* (xsp->len + X_EXTRA) never overflows */ + checkoktoadd(more, xsp->len + X_EXTRA); + xsp->beg = aresize(xsp->beg, (xsp->len += more) + X_EXTRA, xsp->areap); xsp->end = xsp->beg + xsp->len; return (xsp->beg + (xp - old_beg)); } @@ -124,12 +141,12 @@ struct options_info { int opts[NELEM(options)]; }; -static char *options_fmt_entry(char *, int, int, const void *); +static char *options_fmt_entry(char *, size_t, int, const void *); static void printoptions(bool); /* format a single select menu item */ static char * -options_fmt_entry(char *buf, int buflen, int i, const void *arg) +options_fmt_entry(char *buf, size_t buflen, int i, const void *arg) { const struct options_info *oi = (const struct options_info *)arg; @@ -142,17 +159,17 @@ options_fmt_entry(char *buf, int buflen, int i, const void *arg) static void printoptions(bool verbose) { - int i = 0; + size_t i = 0; if (verbose) { - int n = 0, len, octs = 0; + ssize_t n = 0, len, octs = 0; struct options_info oi; /* verbose version */ shf_puts("Current option settings\n", shl_stdout); oi.opt_width = 0; - while (i < (int)NELEM(options)) { + while (i < NELEM(options)) { if (options[i].name) { oi.opts[n++] = i; len = strlen(options[i].name); @@ -167,11 +184,12 @@ printoptions(bool verbose) print_columns(shl_stdout, n, options_fmt_entry, &oi, octs + 4, oi.opt_width + 4, true); } else { - /* short version á la AT&T ksh93 */ - shf_puts("set", shl_stdout); + /* short version like AT&T ksh93 */ + shf_puts(Tset, shl_stdout); while (i < (int)NELEM(options)) { if (Flag(i) && options[i].name) - shprintf(" -o %s", options[i].name); + shprintf("%s %s %s", null, "-o", + options[i].name); ++i; } shf_putc('\n', shl_stdout); @@ -181,8 +199,8 @@ printoptions(bool verbose) char * getoptions(void) { - unsigned int i; - char m[(int) FNFLAGS + 1]; + size_t i; + char m[(int)FNFLAGS + 1]; char *cp = m; for (i = 0; i < NELEM(options); i++) @@ -199,7 +217,8 @@ change_flag(enum sh_flag f, int what, unsigned int newval) unsigned char oldval; oldval = Flag(f); - Flag(f) = newval ? 1 : 0; /* needed for tristates */ + /* needed for tristates */ + Flag(f) = newval ? 1 : 0; #ifndef MKSH_UNEMPLOYED if (f == FMONITOR) { if (what != OF_CMDLINE && newval != oldval) @@ -218,18 +237,21 @@ change_flag(enum sh_flag f, int what, unsigned int newval) Flag(f) = (unsigned char)newval; } else if (f == FPRIVILEGED && oldval && !newval) { /* Turning off -p? */ -#if HAVE_SETRESUGID - gid_t kshegid = getgid(); - setresgid(kshegid, kshegid, kshegid); + /*XXX this can probably be optimised */ + kshegid = kshgid = getgid(); +#if HAVE_SETRESUGID + DO_SETUID(setresgid, (kshegid, kshegid, kshegid)); #if HAVE_SETGROUPS + /* setgroups doesn't EAGAIN on Linux */ setgroups(1, &kshegid); #endif - setresuid(ksheuid, ksheuid, ksheuid); + DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid)); #else + /* seteuid, setegid, setgid don't EAGAIN on Linux */ seteuid(ksheuid = kshuid = getuid()); - setuid(ksheuid); - setegid(kshegid = kshgid = getgid()); + DO_SETUID(setuid, (ksheuid)); + setegid(kshegid); setgid(kshegid); #endif } else if ((f == FPOSIX || f == FSH) && newval) { @@ -243,12 +265,14 @@ change_flag(enum sh_flag f, int what, unsigned int newval) } } -/* Parse command line & set command arguments. Returns the index of +/* + * Parse command line and set command arguments. Returns the index of * non-option arguments, -1 if there is an error. */ int parse_args(const char **argv, - int what, /* OF_CMDLINE or OF_SET */ + /* OF_CMDLINE or OF_SET */ + int what, bool *setargsp) { static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */ @@ -257,7 +281,8 @@ parse_args(const char **argv, const char *array = NULL; Getopt go; size_t i; - int optc, sortargs = 0, arrayset = 0; + int optc, arrayset = 0; + bool sortargs = false; /* First call? Build option strings... */ if (cmd_opts[0] == '\0') { @@ -291,7 +316,8 @@ parse_args(const char **argv, if (what == OF_CMDLINE) { const char *p = argv[0], *q; - /* Set FLOGIN before parsing options so user can clear + /* + * Set FLOGIN before parsing options so user can clear * flag using +l. */ if (*p != '-') @@ -319,7 +345,8 @@ parse_args(const char **argv, if (what == OF_FIRSTTIME) break; if (go.optarg == NULL) { - /* lone -o: print options + /* + * lone -o: print options * * Note that on the command line, -o requires * an option (ie, can't get here if what is @@ -329,14 +356,9 @@ parse_args(const char **argv, break; } i = option(go.optarg); - if ((enum sh_flag)i == FARC4RANDOM) { - warningf(true, "Do not use set ±o arc4random," - " it will be removed in the next version" - " of mksh!"); - return (0); - } if ((i != (size_t)-1) && set == Flag(i)) - /* Don't check the context if the flag + /* + * Don't check the context if the flag * isn't changing - makes "set -o interactive" * work if you're already interactive. Needed * if the output of "set +o" is to be used. @@ -345,7 +367,7 @@ parse_args(const char **argv, else if ((i != (size_t)-1) && (options[i].flags & what)) change_flag((enum sh_flag)i, what, set); else { - bi_errorf("%s: bad option", go.optarg); + bi_errorf("%s: %s", go.optarg, "bad option"); return (-1); } break; @@ -371,7 +393,7 @@ parse_args(const char **argv, break; /* -s: sort positional params (AT&T ksh stupidity) */ if (what == OF_SET && optc == 's') { - sortargs = 1; + sortargs = true; break; } for (i = 0; i < NELEM(options); i++) @@ -398,9 +420,15 @@ parse_args(const char **argv, *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) || argv[go.optind]); - if (arrayset && (!*array || *skip_varname(array, false))) { - bi_errorf("%s: is not an identifier", array); - return (-1); + if (arrayset) { + const char *ccp = NULL; + + if (*array) + ccp = skip_varname(array, false); + if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) { + bi_errorf("%s: %s", array, "is not an identifier"); + return (-1); + } } if (sortargs) { for (i = go.optind; argv[i]; i++) @@ -409,7 +437,7 @@ parse_args(const char **argv, xstrcmp); } if (arrayset) - go.optind += set_array(array, arrayset > 0 ? true : false, + go.optind += set_array(array, tobool(arrayset > 0), argv + go.optind); return (go.optind); @@ -456,10 +484,83 @@ bi_getn(const char *as, int *ai) int rv; if (!(rv = getn(as, ai))) - bi_errorf("%s: bad number", as); + bi_errorf("%s: %s", as, "bad number"); return (rv); } +/** + * pattern simplifications: + * - @(x) -> x (not @(x|y) though) + * - ** -> * + */ +static void * +simplify_gmatch_pattern(const unsigned char *sp) +{ + uint8_t c; + unsigned char *cp, *dp; + const unsigned char *ps, *se; + + cp = alloc(strlen((const void *)sp) + 1, ATEMP); + goto simplify_gmatch_pat1a; + + /* foo@(b@(a)r)b@(a|a)z -> foobarb@(a|a)z */ + simplify_gmatch_pat1: + sp = cp; + simplify_gmatch_pat1a: + dp = cp; + se = sp + strlen((const void *)sp); + while ((c = *sp++)) { + if (!ISMAGIC(c)) { + *dp++ = c; + continue; + } + switch ((c = *sp++)) { + case 0x80|'@': + /* simile for @ */ + case 0x80|' ': + /* check whether it has only one clause */ + ps = pat_scan(sp, se, true); + if (!ps || ps[-1] != /*(*/ ')') + /* nope */ + break; + /* copy inner clause until matching close */ + ps -= 2; + while ((const unsigned char *)sp < ps) + *dp++ = *sp++; + /* skip MAGIC and closing parenthesis */ + sp += 2; + /* copy the rest of the pattern */ + memmove(dp, sp, strlen((const void *)sp) + 1); + /* redo from start */ + goto simplify_gmatch_pat1; + } + *dp++ = MAGIC; + *dp++ = c; + } + *dp = '\0'; + + /* collapse adjacent asterisk wildcards */ + sp = dp = cp; + while ((c = *sp++)) { + if (!ISMAGIC(c)) { + *dp++ = c; + continue; + } + switch ((c = *sp++)) { + case '*': + while (ISMAGIC(sp[0]) && sp[1] == c) + sp += 2; + break; + } + *dp++ = MAGIC; + *dp++ = c; + } + *dp = '\0'; + + /* return the result, allocated from ATEMP */ + return (cp); +} + /* -------- gmatch.c -------- */ /* @@ -469,18 +570,20 @@ bi_getn(const char *as, int *ai) * Match a pattern as in sh(1). * pattern character are prefixed with MAGIC by expand. */ - int gmatchx(const char *s, const char *p, bool isfile) { const char *se, *pe; + char *pnew; + int rv; if (s == NULL || p == NULL) return (0); se = s + strlen(s); pe = p + strlen(p); - /* isfile is false iff no syntax check has been done on + /* + * isfile is false iff no syntax check has been done on * the pattern. If check fails, just to a strcmp(). */ if (!isfile && !has_globbing(p, pe)) { @@ -490,11 +593,22 @@ gmatchx(const char *s, const char *p, bool isfile) debunk(t, p, len); return (!strcmp(t, s)); } - return (do_gmatch((const unsigned char *) s, (const unsigned char *) se, - (const unsigned char *) p, (const unsigned char *) pe)); + + /* + * since the do_gmatch() engine sucks so much, we must do some + * pattern simplifications + */ + pnew = simplify_gmatch_pattern((const unsigned char *)p); + pe = pnew + strlen(pnew); + + rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se, + (const unsigned char *)pnew, (const unsigned char *)pe); + afree(pnew, ATEMP); + return (rv); } -/* Returns if p is a syntacticly correct globbing pattern, false +/** + * Returns if p is a syntacticly correct globbing pattern, false * if it contains no pattern characters or if there is a syntax error. * Syntax errors are: * - [ with no closing ] @@ -502,14 +616,14 @@ gmatchx(const char *s, const char *p, bool isfile) * - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d)) */ /*XXX -- if no magic, - if dest given, copy to dst - return ? -- if magic && (no globbing || syntax error) - debunk to dst - return ? -- return ? -*/ + * - if no magic, + * if dest given, copy to dst + * return ? + * - if magic && (no globbing || syntax error) + * debunk to dst + * return ? + * - return ? + */ int has_globbing(const char *xp, const char *xpe) { @@ -517,42 +631,46 @@ has_globbing(const char *xp, const char *xpe) const unsigned char *pe = (const unsigned char *) xpe; int c; int nest = 0, bnest = 0; - int saw_glob = 0; - int in_bracket = 0; /* inside [...] */ + bool saw_glob = false; + /* inside [...] */ + bool in_bracket = false; for (; p < pe; p++) { if (!ISMAGIC(*p)) continue; if ((c = *++p) == '*' || c == '?') - saw_glob = 1; + saw_glob = true; else if (c == '[') { if (!in_bracket) { - saw_glob = 1; - in_bracket = 1; + saw_glob = true; + in_bracket = true; if (ISMAGIC(p[1]) && p[2] == NOT) p += 2; if (ISMAGIC(p[1]) && p[2] == ']') p += 2; } - /* XXX Do we need to check ranges here? POSIX Q */ + /*XXX Do we need to check ranges here? POSIX Q */ } else if (c == ']') { if (in_bracket) { - if (bnest) /* [a*(b]) */ + if (bnest) + /* [a*(b]) */ return (0); - in_bracket = 0; + in_bracket = false; } } else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) { - saw_glob = 1; + saw_glob = true; if (in_bracket) bnest++; else nest++; } else if (c == '|') { - if (in_bracket && !bnest) /* *(a[foo|bar]) */ + if (in_bracket && !bnest) + /* *(a[foo|bar]) */ return (0); } else if (c == /*(*/ ')') { if (in_bracket) { - if (!bnest--) /* *(a[b)c] */ + if (!bnest--) + /* *(a[b)c] */ return (0); } else if (nest) nest--; @@ -614,9 +732,12 @@ do_gmatch(const unsigned char *s, const unsigned char *se, * [*+?@!](pattern|pattern|..) * This is also needed for ${..%..}, etc. */ - case 0x80|'+': /* matches one or more times */ - case 0x80|'*': /* matches zero or more times */ - if (!(prest = pat_scan(p, pe, 0))) + + /* matches one or more times */ + case 0x80|'+': + /* matches zero or more times */ + case 0x80|'*': + if (!(prest = pat_scan(p, pe, false))) return (0); s--; /* take care of zero matches */ @@ -624,7 +745,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se, do_gmatch(s, se, prest, pe)) return (1); for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); + pnext = pat_scan(psub, pe, true); for (srest = s; srest <= se; srest++) { if (do_gmatch(s, srest, psub, pnext - 2) && (do_gmatch(srest, se, prest, pe) || @@ -637,10 +758,13 @@ do_gmatch(const unsigned char *s, const unsigned char *se, } return (0); - case 0x80|'?': /* matches zero or once */ - case 0x80|'@': /* matches one of the patterns */ - case 0x80|' ': /* simile for @ */ - if (!(prest = pat_scan(p, pe, 0))) + /* matches zero or once */ + case 0x80|'?': + /* matches one of the patterns */ + case 0x80|'@': + /* simile for @ */ + case 0x80|' ': + if (!(prest = pat_scan(p, pe, false))) return (0); s--; /* Take care of zero matches */ @@ -648,7 +772,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se, do_gmatch(s, se, prest, pe)) return (1); for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); + pnext = pat_scan(psub, pe, true); srest = prest == pe ? se : s; for (; srest <= se; srest++) { if (do_gmatch(s, srest, psub, pnext - 2) && @@ -660,15 +784,16 @@ do_gmatch(const unsigned char *s, const unsigned char *se, } return (0); - case 0x80|'!': /* matches none of the patterns */ - if (!(prest = pat_scan(p, pe, 0))) + /* matches none of the patterns */ + case 0x80|'!': + if (!(prest = pat_scan(p, pe, false))) return (0); s--; for (srest = s; srest <= se; srest++) { int matched = 0; for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); + pnext = pat_scan(psub, pe, true); if (do_gmatch(s, srest, psub, pnext - 2)) { matched = 1; @@ -705,9 +830,11 @@ cclass(const unsigned char *p, int sub) if (ISMAGIC(c)) { c = *p++; if ((c & 0x80) && !ISMAGIC(c)) { - c &= 0x7f;/* extended pattern matching: *+?@! */ + /* extended pattern matching: *+?@! */ + c &= 0x7F; /* XXX the ( char isn't handled as part of [] */ - if (c == ' ') /* simile for @: plain (..) */ + if (c == ' ') + /* simile for @: plain (..) */ c = '(' /*)*/; } } @@ -716,7 +843,8 @@ cclass(const unsigned char *p, int sub) return (sub == '[' ? orig_p : NULL); if (ISMAGIC(p[0]) && p[1] == '-' && (!ISMAGIC(p[2]) || p[3] != ']')) { - p += 2; /* MAGIC- */ + /* MAGIC- */ + p += 2; d = *p++; if (ISMAGIC(d)) { d = *p++; @@ -736,8 +864,8 @@ cclass(const unsigned char *p, int sub) } /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */ -const unsigned char * -pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep) +static const unsigned char * +pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep) { int nest = 0; @@ -772,7 +900,8 @@ ksh_getopt_reset(Getopt *go, int flags) } -/* getopt() used for shell built-in commands, the getopts command, and +/** + * getopt() used for shell built-in commands, the getopts command, and * command line options. * A leading ':' in options means don't print errors, instead return '?' * or ':' and set go->optarg to the offending option character. @@ -813,7 +942,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) return (-1); } if (arg == NULL || - ((flag != '-' ) && /* neither a - nor a + (if + allowed) */ + ((flag != '-' ) && + /* neither a - nor a + (if + allowed) */ (!(go->flags & GF_PLUSOPT) || flag != '+')) || (c = arg[1]) == '\0') { go->p = 0; @@ -830,15 +960,17 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) go->buf[0] = c; go->optarg = go->buf; } else { - warningf(true, "%s%s-%c: unknown option", + warningf(true, "%s%s-%c: %s", (go->flags & GF_NONAME) ? "" : argv[0], - (go->flags & GF_NONAME) ? "" : ": ", c); + (go->flags & GF_NONAME) ? "" : ": ", c, + "unknown option"); if (go->flags & GF_ERROR) bi_errorfz(); } return ('?'); } - /* : means argument must be present, may be part of option argument + /** + * : means argument must be present, may be part of option argument * or the next argument * ; same as : but argument may be missing * , means argument is part of option argument, and may be null. @@ -856,9 +988,10 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) go->optarg = go->buf; return (':'); } - warningf(true, "%s%s-'%c' requires argument", + warningf(true, "%s%s-%c: %s", (go->flags & GF_NONAME) ? "" : argv[0], - (go->flags & GF_NONAME) ? "" : ": ", c); + (go->flags & GF_NONAME) ? "" : ": ", c, + "requires an argument"); if (go->flags & GF_ERROR) bi_errorfz(); return ('?'); @@ -869,7 +1002,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) go->optarg = argv[go->optind - 1] + go->p; go->p = 0; } else if (*o == '#') { - /* argument is optional and may be attached or unattached + /* + * argument is optional and may be attached or unattached * but must start with a digit. optarg is set to 0 if the * argument is missing. */ @@ -890,7 +1024,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) return (c); } -/* print variable/alias value using necessary quotes +/* + * print variable/alias value using necessary quotes * (POSIX says they should be suitable for re-entry...) * No trailing newline is printed. */ @@ -898,25 +1033,33 @@ void print_value_quoted(const char *s) { const char *p; - int inquote = 0; + bool inquote = false; - /* Test if any quotes are needed */ + /* first, check whether any quotes are needed */ for (p = s; *p; p++) if (ctype(*p, C_QUOTE)) break; if (!*p) { + /* nope, use the shortcut */ shf_puts(s, shl_stdout); return; } + + /* quote via state machine */ for (p = s; *p; p++) { if (*p == '\'') { - if (inquote) + /* + * multiple '''s or any ' at beginning of string + * look nicer this way than when simply substituting + */ + if (inquote) { shf_putc('\'', shl_stdout); + inquote = false; + } shf_putc('\\', shl_stdout); - inquote = 0; } else if (!inquote) { shf_putc('\'', shl_stdout); - inquote = 1; + inquote = true; } shf_putc(*p, shl_stdout); } @@ -930,10 +1073,10 @@ print_value_quoted(const char *s) */ void print_columns(struct shf *shf, int n, - char *(*func)(char *, int, int, const void *), - const void *arg, int max_oct, int max_col, bool prefcol) + char *(*func)(char *, size_t, int, const void *), + const void *arg, size_t max_oct, size_t max_colz, bool prefcol) { - int i, r, c, rows, cols, nspace; + int i, r, c, rows, cols, nspace, max_col; char *str; if (n <= 0) { @@ -943,6 +1086,15 @@ print_columns(struct shf *shf, int n, return; } + if (max_colz > 2147483647) { +#ifndef MKSH_SMALL + internal_warningf("print_columns called with max_col=%zu > INT_MAX", + max_colz); +#endif + return; + } + max_col = (int)max_colz; + ++max_oct; str = alloc(max_oct, ATEMP); @@ -998,8 +1150,9 @@ strip_nuls(char *buf, int nbytes) { char *dst; - /* nbytes check because some systems (older FreeBSDs) have a buggy - * memchr() + /* + * nbytes check because some systems (older FreeBSDs) have a + * buggy memchr() */ if (nbytes && (dst = memchr(buf, '\0', nbytes))) { char *end = buf + nbytes; @@ -1019,19 +1172,20 @@ strip_nuls(char *buf, int nbytes) } } -/* Like read(2), but if read fails due to non-blocking flag, resets flag - * and restarts read. +/* + * Like read(2), but if read fails due to non-blocking flag, + * resets flag and restarts read. */ -int -blocking_read(int fd, char *buf, int nbytes) +ssize_t +blocking_read(int fd, char *buf, size_t nbytes) { - int ret; - int tried_reset = 0; + ssize_t ret; + bool tried_reset = false; while ((ret = read(fd, buf, nbytes)) < 0) { if (!tried_reset && errno == EAGAIN) { if (reset_nonblock(fd) > 0) { - tried_reset = 1; + tried_reset = true; continue; } errno = EAGAIN; @@ -1041,7 +1195,8 @@ blocking_read(int fd, char *buf, int nbytes) return (ret); } -/* Reset the non-blocking flag on the specified file descriptor. +/* + * Reset the non-blocking flag on the specified file descriptor. * Returns -1 if there was an error, 0 if non-blocking wasn't set, * 1 if it was. */ @@ -1060,34 +1215,225 @@ reset_nonblock(int fd) return (1); } - -/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */ +/* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */ char * -ksh_get_wd(size_t *dlen) +ksh_get_wd(void) { - char *ret, *b; - size_t len = 1; - #ifdef NO_PATH_MAX - if ((b = get_current_dir_name())) { - len = strlen(b) + 1; - strndupx(ret, b, len - 1, ATEMP); - free(b); + char *rv, *cp; + + if ((cp = get_current_dir_name())) { + strdupx(rv, cp, ATEMP); + free_gnu_gcdn(cp); } else - ret = NULL; + rv = NULL; #else - if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX))) - ret = aresize(b, len = (strlen(b) + 1), ATEMP); - else - afree(b, ATEMP); + char *rv; + + if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) { + afree(rv, ATEMP); + rv = NULL; + } #endif - if (dlen) - *dlen = len; - return (ret); + return (rv); } -/* +char * +do_realpath(const char *upath) +{ + char *xp, *ip, *tp, *ipath, *ldest = NULL; + XString xs; + ptrdiff_t pos; + size_t len; + int llen; + struct stat sb; +#ifdef NO_PATH_MAX + size_t ldestlen = 0; +#define pathlen sb.st_size +#define pathcnd (ldestlen < (pathlen + 1)) +#else +#define pathlen PATH_MAX +#define pathcnd (!ldest) +#endif + /* max. recursion depth */ + int symlinks = 32; + + if (upath[0] == '/') { + /* upath is an absolute pathname */ + strdupx(ipath, upath, ATEMP); + } else { + /* upath is a relative pathname, prepend cwd */ + if ((tp = ksh_get_wd()) == NULL || tp[0] != '/') + return (NULL); + ipath = shf_smprintf("%s%s%s", tp, "/", upath); + afree(tp, ATEMP); + } + + /* ipath and upath are in memory at the same time -> unchecked */ + Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); + + /* now jump into the deep of the loop */ + goto beginning_of_a_pathname; + + while (*ip) { + /* skip slashes in input */ + while (*ip == '/') + ++ip; + if (!*ip) + break; + + /* get next pathname component from input */ + tp = ip; + while (*ip && *ip != '/') + ++ip; + len = ip - tp; + + /* check input for "." and ".." */ + if (tp[0] == '.') { + if (len == 1) + /* just continue with the next one */ + continue; + else if (len == 2 && tp[1] == '.') { + /* strip off last pathname component */ + while (xp > Xstring(xs, xp)) + if (*--xp == '/') + break; + /* then continue with the next one */ + continue; + } + } + + /* store output position away, then append slash to output */ + pos = Xsavepos(xs, xp); + /* 1 for the '/' and len + 1 for tp and the NUL from below */ + XcheckN(xs, xp, 1 + len + 1); + Xput(xs, xp, '/'); + + /* append next pathname component to output */ + memcpy(xp, tp, len); + xp += len; + *xp = '\0'; + + /* lstat the current output, see if it's a symlink */ + if (lstat(Xstring(xs, xp), &sb)) { + /* lstat failed */ + if (errno == ENOENT) { + /* because the pathname does not exist */ + while (*ip == '/') + /* skip any trailing slashes */ + ++ip; + /* no more components left? */ + if (!*ip) + /* we can still return successfully */ + break; + /* more components left? fall through */ + } + /* not ENOENT or not at the end of ipath */ + goto notfound; + } + + /* check if we encountered a symlink? */ + if (S_ISLNK(sb.st_mode)) { + /* reached maximum recursion depth? */ + if (!symlinks--) { + /* yep, prevent infinite loops */ + errno = ELOOP; + goto notfound; + } + + /* get symlink(7) target */ + if (pathcnd) { +#ifdef NO_PATH_MAX + if (notoktoadd(pathlen, 1)) { + errno = ENAMETOOLONG; + goto notfound; + } +#endif + ldest = aresize(ldest, pathlen + 1, ATEMP); + } + llen = readlink(Xstring(xs, xp), ldest, pathlen); + if (llen < 0) + /* oops... */ + goto notfound; + ldest[llen] = '\0'; + + /* + * restart if symlink target is an absolute path, + * otherwise continue with currently resolved prefix + */ + /* append rest of current input path to link target */ + tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip); + afree(ipath, ATEMP); + ip = ipath = tp; + if (ldest[0] != '/') { + /* symlink target is a relative path */ + xp = Xrestpos(xs, xp, pos); + } else { + /* symlink target is an absolute path */ + xp = Xstring(xs, xp); + beginning_of_a_pathname: + /* assert: (ip == ipath)[0] == '/' */ + /* assert: xp == xs.beg => start of path */ + + /* exactly two leading slashes? (SUSv4 3.266) */ + if (ip[1] == '/' && ip[2] != '/') { + /* keep them, e.g. for UNC pathnames */ + Xput(xs, xp, '/'); + } + } + } + /* otherwise (no symlink) merely go on */ + } + + /* + * either found the target and successfully resolved it, + * or found its parent directory and may create it + */ + if (Xlength(xs, xp) == 0) + /* + * if the resolved pathname is "", make it "/", + * otherwise do not add a trailing slash + */ + Xput(xs, xp, '/'); + Xput(xs, xp, '\0'); + + /* + * if source path had a trailing slash, check if target path + * is not a non-directory existing file + */ + if (ip > ipath && ip[-1] == '/') { + if (stat(Xstring(xs, xp), &sb)) { + if (errno != ENOENT) + goto notfound; + } else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto notfound; + } + /* target now either does not exist or is a directory */ + } + + /* return target path */ + if (ldest != NULL) + afree(ldest, ATEMP); + afree(ipath, ATEMP); + return (Xclose(xs, xp)); + + notfound: + /* save; freeing memory might trash it */ + llen = errno; + if (ldest != NULL) + afree(ldest, ATEMP); + afree(ipath, ATEMP); + Xfree(xs, xp); + errno = llen; + return (NULL); + +#undef pathlen +#undef pathcnd +} + +/** * Makes a filename into result using the following algorithm. * - make result NULL * - if file starts with '/', append file to result & set cdpathp to NULL @@ -1102,16 +1448,17 @@ ksh_get_wd(size_t *dlen) * The return value indicates whether a non-null element from cdpathp * was appended to result. */ -int +static int make_path(const char *cwd, const char *file, - char **cdpathp, /* & of : separated list */ + /* pointer to colon-separated list */ + char **cdpathp, XString *xsp, int *phys_pathp) { int rval = 0; bool use_cdpath = true; char *plist; - int len, plen = 0; + size_t len, plen = 0; char *xp = Xstring(*xsp, xp); if (!file) @@ -1172,96 +1519,296 @@ make_path(const char *cwd, const char *file, return (rval); } -/* +/*- * Simplify pathnames containing "." and ".." entries. - * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b" + * + * simplify_path(this) = that + * /a/b/c/./../d/.. /a/b + * //./C/foo/bar/../baz //C/foo/baz + * /foo/ /foo + * /foo/../../bar /bar + * /foo/./blah/.. /foo + * . . + * .. .. + * ./foo foo + * foo/../../../bar ../../bar */ void -simplify_path(char *pathl) +simplify_path(char *p) { - char *cur, *t; - bool isrooted; - char *very_start = pathl, *start; + char *dp, *ip, *sp, *tp; + size_t len; + bool needslash; - if (!*pathl) + switch (*p) { + case 0: return; + case '/': + /* exactly two leading slashes? (SUSv4 3.266) */ + if (p[1] == '/' && p[2] != '/') + /* keep them, e.g. for UNC pathnames */ + ++p; + needslash = true; + break; + default: + needslash = false; + } + dp = ip = sp = p; - if ((isrooted = pathl[0] == '/')) - very_start++; - - /* Before After - * /foo/ /foo - * /foo/../../bar /bar - * /foo/./blah/.. /foo - * . . - * .. .. - * ./foo foo - * foo/../../../bar ../../bar - */ - - for (cur = t = start = very_start; ; ) { - /* treat multiple '/'s as one '/' */ - while (*t == '/') - t++; - - if (*t == '\0') { - if (cur == pathl) - /* convert empty path to dot */ - *cur++ = '.'; - *cur = '\0'; + while (*ip) { + /* skip slashes in input */ + while (*ip == '/') + ++ip; + if (!*ip) break; - } - if (t[0] == '.') { - if (!t[1] || t[1] == '/') { - t += 1; + /* get next pathname component from input */ + tp = ip; + while (*ip && *ip != '/') + ++ip; + len = ip - tp; + + /* check input for "." and ".." */ + if (tp[0] == '.') { + if (len == 1) + /* just continue with the next one */ continue; - } else if (t[1] == '.' && (!t[2] || t[2] == '/')) { - if (!isrooted && cur == start) { - if (cur != very_start) - *cur++ = '/'; - *cur++ = '.'; - *cur++ = '.'; - start = cur; - } else if (cur != start) - while (--cur > start && *cur != '/') - ; - t += 2; + else if (len == 2 && tp[1] == '.') { + /* parent level, but how? */ + if (*p == '/') + /* absolute path, only one way */ + goto strip_last_component; + else if (dp > sp) { + /* relative path, with subpaths */ + needslash = false; + strip_last_component: + /* strip off last pathname component */ + while (dp > sp) + if (*--dp == '/') + break; + } else { + /* relative path, at its beginning */ + if (needslash) + /* or already dotdot-slash'd */ + *dp++ = '/'; + /* keep dotdot-slash if not absolute */ + *dp++ = '.'; + *dp++ = '.'; + needslash = true; + sp = dp; + } + /* then continue with the next one */ continue; } } - if (cur != very_start) - *cur++ = '/'; + if (needslash) + *dp++ = '/'; - /* find/copy next component of pathname */ - while (*t && *t != '/') - *cur++ = *t++; + /* append next pathname component to output */ + memmove(dp, tp, len); + dp += len; + + /* append slash if we continue */ + needslash = true; + /* try next component */ } + if (dp == p) + /* empty path -> dot */ + *dp++ = needslash ? '/' : '.'; + *dp = '\0'; } - void -set_current_wd(char *pathl) +set_current_wd(const char *nwd) { - size_t len = 1; - char *p = pathl; + char *allocd = NULL; - if (p == NULL) { - if ((p = ksh_get_wd(&len)) == NULL) - p = null; - } else - len = strlen(p) + 1; + if (nwd == NULL) { + allocd = ksh_get_wd(); + nwd = allocd ? allocd : null; + } + + afree(current_wd, APERM); + strdupx(current_wd, nwd, APERM); + + afree(allocd, ATEMP); +} + +int +c_cd(const char **wp) +{ + int optc, rv, phys_path; + bool physical = tobool(Flag(FPHYSICAL)); + /* was a node from cdpath added in? */ + int cdnode; + /* show where we went?, error for $PWD */ + bool printpath = false, eflag = false; + struct tbl *pwd_s, *oldpwd_s; + XString xs; + char *dir, *allocd = NULL, *tryp, *pwd, *cdpath; + + while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1) + switch (optc) { + case 'e': + eflag = true; + break; + case 'L': + physical = false; + break; + case 'P': + physical = true; + break; + case '?': + return (2); + } + wp += builtin_opt.optind; + + if (Flag(FRESTRICTED)) { + bi_errorf("restricted shell - can't cd"); + return (2); + } + + pwd_s = global("PWD"); + oldpwd_s = global("OLDPWD"); + + if (!wp[0]) { + /* No arguments - go home */ + if ((dir = str_val(global("HOME"))) == null) { + bi_errorf("no home directory (HOME not set)"); + return (2); + } + } else if (!wp[1]) { + /* One argument: - or dir */ + strdupx(allocd, wp[0], ATEMP); + if (ksh_isdash((dir = allocd))) { + afree(allocd, ATEMP); + allocd = NULL; + dir = str_val(oldpwd_s); + if (dir == null) { + bi_errorf("no OLDPWD"); + return (2); + } + printpath = true; + } + } else if (!wp[2]) { + /* Two arguments - substitute arg1 in PWD for arg2 */ + size_t ilen, olen, nlen, elen; + char *cp; + + if (!current_wd[0]) { + bi_errorf("can't determine current directory"); + return (2); + } + /* + * substitute arg1 for arg2 in current path. + * if the first substitution fails because the cd fails + * we could try to find another substitution. For now + * we don't + */ + if ((cp = strstr(current_wd, wp[0])) == NULL) { + bi_errorf("bad substitution"); + return (2); + } + /*- + * ilen = part of current_wd before wp[0] + * elen = part of current_wd after wp[0] + * because current_wd and wp[1] need to be in memory at the + * same time beforehand the addition can stay unchecked + */ + ilen = cp - current_wd; + olen = strlen(wp[0]); + nlen = strlen(wp[1]); + elen = strlen(current_wd + ilen + olen) + 1; + dir = allocd = alloc(ilen + nlen + elen, ATEMP); + memcpy(dir, current_wd, ilen); + memcpy(dir + ilen, wp[1], nlen); + memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen); + printpath = true; + } else { + bi_errorf("too many arguments"); + return (2); + } + +#ifdef NO_PATH_MAX + /* only a first guess; make_path will enlarge xs if necessary */ + XinitN(xs, 1024, ATEMP); +#else + XinitN(xs, PATH_MAX, ATEMP); +#endif + + cdpath = str_val(global("CDPATH")); + do { + cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); + if (physical) + rv = chdir(tryp = Xstring(xs, xp) + phys_path); + else { + simplify_path(Xstring(xs, xp)); + rv = chdir(tryp = Xstring(xs, xp)); + } + } while (rv < 0 && cdpath != NULL); + + if (rv < 0) { + if (cdnode) + bi_errorf("%s: %s", dir, "bad directory"); + else + bi_errorf("%s: %s", tryp, strerror(errno)); + afree(allocd, ATEMP); + Xfree(xs, xp); + return (2); + } + + rv = 0; + + /* allocd (above) => dir, which is no longer used */ + afree(allocd, ATEMP); + allocd = NULL; + + /* Clear out tracked aliases with relative paths */ + flushcom(false); + + /* + * Set OLDPWD (note: unsetting OLDPWD does not disable this + * setting in AT&T ksh) + */ + if (current_wd[0]) + /* Ignore failure (happens if readonly or integer) */ + setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); + + if (Xstring(xs, xp)[0] != '/') { + pwd = NULL; + } else if (!physical) { + goto norealpath_PWD; + } else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) { + if (eflag) + rv = 1; + norealpath_PWD: + pwd = Xstring(xs, xp); + } + + /* Set PWD */ + if (pwd) { + char *ptmp = pwd; - if (len > current_wd_size) { - afree(current_wd, APERM); - current_wd = alloc(current_wd_size = len, APERM); + set_current_wd(ptmp); + /* Ignore failure (happens if readonly or integer) */ + setstr(pwd_s, ptmp, KSH_RETURN_ERROR); + } else { + set_current_wd(null); + pwd = Xstring(xs, xp); + /* XXX unset $PWD? */ + if (eflag) + rv = 1; } - memcpy(current_wd, p, len); - if (p != pathl && p != null) - afree(p, ATEMP); + if (printpath || cdnode) + shprintf("%s\n", pwd); + + afree(allocd, ATEMP); + Xfree(xs, xp); + return (rv); } + #ifdef TIOCSCTTY extern void chvt_reinit(void); @@ -1272,9 +1819,6 @@ chvt(const char *fn) struct stat sb; int fd; - /* for entropy */ - kshstate_f.h = evilhash(fn); - if (*fn == '-') { memcpy(dv, "-/dev/null", sizeof("-/dev/null")); fn = dv + 1; @@ -1285,56 +1829,75 @@ chvt(const char *fn) if (stat(dv, &sb)) { strlcpy(dv + 8, fn, sizeof(dv) - 8); if (stat(dv, &sb)) - errorf("chvt: can't find tty %s", fn); + errorf("%s: %s %s", "chvt", + "can't find tty", fn); } fn = dv; } if (!(sb.st_mode & S_IFCHR)) - errorf("chvt: not a char device: %s", fn); + errorf("%s %s %s", "chvt: not a char", "device", fn); if ((sb.st_uid != 0) && chown(fn, 0, 0)) - warningf(false, "chvt: cannot chown root %s", fn); + warningf(false, "%s: %s %s", "chvt", "can't chown root", fn); if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600)) - warningf(false, "chvt: cannot chmod 0600 %s", fn); + warningf(false, "%s: %s %s", "chvt", "can't chmod 0600", fn); #if HAVE_REVOKE if (revoke(fn)) #endif - warningf(false, "chvt: cannot revoke %s, new shell is" - " potentially insecure", fn); + warningf(false, "%s: %s %s", "chvt", + "new shell is potentially insecure, can't revoke", + fn); } if ((fd = open(fn, O_RDWR)) == -1) { sleep(1); if ((fd = open(fn, O_RDWR)) == -1) - errorf("chvt: cannot open %s", fn); + errorf("%s: %s %s", "chvt", "can't open", fn); } switch (fork()) { case -1: - errorf("chvt: %s failed", "fork"); + errorf("%s: %s %s", "chvt", "fork", "failed"); case 0: break; default: exit(0); } if (setsid() == -1) - errorf("chvt: %s failed", "setsid"); + errorf("%s: %s %s", "chvt", "setsid", "failed"); if (fn != dv + 1) { if (ioctl(fd, TIOCSCTTY, NULL) == -1) - errorf("chvt: %s failed", "TIOCSCTTY"); + errorf("%s: %s %s", "chvt", "TIOCSCTTY", "failed"); if (tcflush(fd, TCIOFLUSH)) - errorf("chvt: %s failed", "TCIOFLUSH"); + errorf("%s: %s %s", "chvt", "TCIOFLUSH", "failed"); } ksh_dup2(fd, 0, false); ksh_dup2(fd, 1, false); ksh_dup2(fd, 2, false); if (fd > 2) close(fd); + { + register uint32_t h; + + NZATInit(h); + NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate)); + NZAATFinish(h); + rndset((long)h); + } chvt_reinit(); } #endif #ifdef DEBUG -char longsizes_are_okay[sizeof(long) == sizeof(unsigned long) ? 1 : -1]; -char arisize_is_okay[sizeof(mksh_ari_t) == 4 ? 1 : -1]; -char uarisize_is_okay[sizeof(mksh_uari_t) == 4 ? 1 : -1]; +#define assert_eq(name, a, b) char name[a == b ? 1 : -1] +#define assert_ge(name, a, b) char name[a >= b ? 1 : -1] +assert_ge(intsize_is_okay, sizeof(int), 4); +assert_eq(intsizes_are_okay, sizeof(int), sizeof(unsigned int)); +assert_ge(longsize_is_okay, sizeof(long), sizeof(int)); +assert_eq(arisize_is_okay, sizeof(mksh_ari_t), 4); +assert_eq(uarisize_is_okay, sizeof(mksh_uari_t), 4); +assert_eq(sizesizes_are_okay, sizeof(size_t), sizeof(ssize_t)); +assert_eq(ptrsizes_are_okay, sizeof(ptrdiff_t), sizeof(void *)); +assert_eq(ptrsize_is_sizet, sizeof(ptrdiff_t), sizeof(size_t)); +/* formatting routines assume this */ +assert_ge(ptr_fits_in_long, sizeof(long), sizeof(size_t)); char * strchr(char *p, int ch) @@ -1367,7 +1930,6 @@ strstr(char *b, const char *l) } #endif -#ifndef MKSH_ASSUME_UTF8 #if !HAVE_STRCASESTR const char * stristr(const char *b, const char *l) @@ -1387,7 +1949,6 @@ stristr(const char *b, const char *l) return (b - 1); } #endif -#endif #ifdef MKSH_SMALL char * @@ -1527,15 +2088,15 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int)) break; case 'U': i = 8; - if (0) + if (/* CONSTCOND */ 0) /* FALLTHROUGH */ case 'u': i = 4; - if (0) + if (/* CONSTCOND */ 0) /* FALLTHROUGH */ case 'x': i = cstyle ? -1 : 2; - /* + /** * x: look for a hexadecimal number with up to * two (C style: arbitrary) digits; convert * to raw octet (C style: Unicode if >0xFF) diff --git a/src/mksh.1 b/src/mksh.1 new file mode 100644 index 0000000..2c70ef0 --- /dev/null +++ b/src/mksh.1 @@ -0,0 +1,6280 @@ +.\" $MirOS: src/bin/mksh/mksh.1,v 1.275 2011/10/07 19:51:29 tg Exp $ +.\" $OpenBSD: ksh.1,v 1.141 2011/09/03 22:59:08 jmc Exp $ +.\"- +.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, +.\" 2010, 2011 +.\" Thorsten Glaser <tg@mirbsd.org> +.\" +.\" Provided that these terms and disclaimer and all copyright notices +.\" are retained or reproduced in an accompanying document, permission +.\" is granted to deal in this work without restriction, including un‐ +.\" limited rights to use, publicly perform, distribute, sell, modify, +.\" merge, give away, or sublicence. +.\" +.\" This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to +.\" the utmost extent permitted by applicable law, neither express nor +.\" implied; without malicious intent or gross negligence. In no event +.\" may a licensor, author or contributor be held liable for indirect, +.\" direct, other damage, loss, or other issues arising in any way out +.\" of dealing in the work, even if advised of the possibility of such +.\" damage or existence of a defect, except proven that it results out +.\" of said person’s immediate fault when using the work as intended. +.\"- +.\" Try to make GNU groff and AT&T nroff more compatible +.\" * ` generates ‘ in gnroff, so use \` +.\" * ' generates ’ in gnroff, \' generates ´, so use \*(aq +.\" * - generates ‐ in gnroff, \- generates −, so .tr it to - +.\" thus use - for hyphens and \- for minus signs and option dashes +.\" * ~ is size-reduced and placed atop in groff, so use \*(TI +.\" * ^ is size-reduced and placed atop in groff, so use \*(ha +.\" * \(en does not work in nroff, so use \*(en +.\" The section after the "doc" macropackage has been loaded contains +.\" additional code to convene between the UCB mdoc macropackage (and +.\" its variant as BSD mdoc in groff) and the GNU mdoc macropackage. +.\" +.ie \n(.g \{\ +. if \*[.T]ascii .tr \-\N'45' +. if \*[.T]latin1 .tr \-\N'45' +. if \*[.T]utf8 .tr \-\N'45' +. ds <= \[<=] +. ds >= \[>=] +. ds Rq \[rq] +. ds Lq \[lq] +. ds sL \(aq +. ds sR \(aq +. if \*[.T]utf8 .ds sL ` +. if \*[.T]ps .ds sL ` +. if \*[.T]utf8 .ds sR ' +. if \*[.T]ps .ds sR ' +. ds aq \(aq +. ds TI \(ti +. ds ha \(ha +. ds en \(en +.\} +.el \{\ +. ds aq ' +. ds TI ~ +. ds ha ^ +. ds en \(em +.\} +.\" +.\" Implement .Dd with the Mdocdate RCS keyword +.\" +.rn Dd xD +.de Dd +.ie \\$1$Mdocdate: \{\ +. xD \\$2 \\$3, \\$4 +.\} +.el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 +.. +.\" +.\" .Dd must come before definition of .Mx, because when called +.\" with -mandoc, it might implement .Mx itself, but we want to +.\" use our own definition. And .Dd must come *first*, always. +.\" +.Dd $Mdocdate: October 7 2011 $ +.\" +.\" Check which macro package we use +.\" +.ie \n(.g \{\ +. ie d volume-ds-1 .ds tT gnu +. el .ds tT bsd +.\} +.el .ds tT ucb +.\" +.\" Implement .Mx (MirBSD) +.\" +.ie "\*(tT"gnu" \{\ +. eo +. de Mx +. nr curr-font \n[.f] +. nr curr-size \n[.ps] +. ds str-Mx \f[\n[curr-font]]\s[\n[curr-size]u] +. ds str-Mx1 \*[Tn-font-size]\%MirOS\*[str-Mx] +. if !\n[arg-limit] \ +. if \n[.$] \{\ +. ds macro-name Mx +. parse-args \$@ +. \} +. if (\n[arg-limit] > \n[arg-ptr]) \{\ +. nr arg-ptr +1 +. ie (\n[type\n[arg-ptr]] == 2) \ +. as str-Mx1 \~\*[arg\n[arg-ptr]] +. el \ +. nr arg-ptr -1 +. \} +. ds arg\n[arg-ptr] "\*[str-Mx1] +. nr type\n[arg-ptr] 2 +. ds space\n[arg-ptr] "\*[space] +. nr num-args (\n[arg-limit] - \n[arg-ptr]) +. nr arg-limit \n[arg-ptr] +. if \n[num-args] \ +. parse-space-vector +. print-recursive +.. +. ec +. ds sP \s0 +. ds tN \*[Tn-font-size] +.\} +.el \{\ +. de Mx +. nr cF \\n(.f +. nr cZ \\n(.s +. ds aa \&\f\\n(cF\s\\n(cZ +. if \\n(aC==0 \{\ +. ie \\n(.$==0 \&MirOS\\*(aa +. el .aV \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 +. \} +. if \\n(aC>\\n(aP \{\ +. nr aP \\n(aP+1 +. ie \\n(C\\n(aP==2 \{\ +. as b1 \&MirOS\ #\&\\*(A\\n(aP\\*(aa +. ie \\n(aC>\\n(aP \{\ +. nr aP \\n(aP+1 +. nR +. \} +. el .aZ +. \} +. el \{\ +. as b1 \&MirOS\\*(aa +. nR +. \} +. \} +.. +.\} +.\"- +.Dt MKSH 1 +.Os MirBSD +.Sh NAME +.Nm mksh , +.Nm sh +.Nd MirBSD Korn shell +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl +abCefhiklmnprUuvXx +.Op Fl T Ar /dev/ttyCn | \- +.Op Fl +o Ar option +.Oo +.Fl c Ar string \*(Ba +.Fl s \*(Ba +.Ar file +.Op Ar argument ... +.Oc +.Ek +.Nm builtin-name +.Op Ar argument ... +.Sh DESCRIPTION +.Nm +is a command interpreter intended for both interactive and shell +script use. +Its command language is a superset of the +.Xr sh C +shell language and largely compatible to the original Korn shell. +.Pp +Most builtins can be called directly, for example if a link points from its +name to the shell; not all make sense, have been tested or work at all though. +.Pp +The options are as follows: +.Bl -tag -width XcXstring +.It Fl c Ar string +.Nm +will execute the command(s) contained in +.Ar string . +.It Fl i +Interactive shell. +A shell is +.Dq interactive +if this +option is used or if both standard input and standard error are attached +to a +.Xr tty 4 . +An interactive shell has job control enabled, ignores the +.Dv SIGINT , +.Dv SIGQUIT , +and +.Dv SIGTERM +signals, and prints prompts before reading input (see the +.Ev PS1 +and +.Ev PS2 +parameters). +It also processes the +.Ev ENV +parameter or the +.Pa mkshrc +file (see below). +For non-interactive shells, the +.Ic trackall +option is on by default (see the +.Ic set +command below). +.It Fl l +Login shell. +If the basename the shell is called with (i.e. argv[0]) +starts with +.Ql \- +or if this option is used, +the shell is assumed to be a login shell; see +.Sx Startup files +below. +.It Fl p +Privileged shell. +A shell is +.Dq privileged +if this option is used +or if the real user ID or group ID does not match the +effective user ID or group ID (see +.Xr getuid 2 +and +.Xr getgid 2 ) . +Clearing the privileged option causes the shell to set +its effective user ID (group ID) to its real user ID (group ID). +For further implications, see +.Sx Startup files . +.It Fl r +Restricted shell. +A shell is +.Dq restricted +if this +option is used. +The following restrictions come into effect after the shell processes any +profile and +.Ev ENV +files: +.Pp +.Bl -bullet -compact +.It +The +.Ic cd +.Po and Ic chdir Pc +command is disabled. +.It +The +.Ev SHELL , +.Ev ENV , +and +.Ev PATH +parameters cannot be changed. +.It +Command names can't be specified with absolute or relative paths. +.It +The +.Fl p +option of the built-in command +.Ic command +can't be used. +.It +Redirections that create files can't be used (i.e.\& +.Ql \*(Gt , +.Ql \*(Gt\*(Ba , +.Ql \*(Gt\*(Gt , +.Ql \*(Lt\*(Gt ) . +.El +.It Fl s +The shell reads commands from standard input; all non-option arguments +are positional parameters. +.It Fl T Ar tty +Spawn +.Nm +on the +.Xr tty 4 +device given. +Superuser only. +If +.Ar tty +is a dash, detach from controlling terminal (daemonise) instead. +.El +.Pp +In addition to the above, the options described in the +.Ic set +built-in command can also be used on the command line: +both +.Op Fl +abCefhkmnuvXx +and +.Op Fl +o Ar option +can be used for single letter or long options, respectively. +.Pp +If neither the +.Fl c +nor the +.Fl s +option is specified, the first non-option argument specifies the name +of a file the shell reads commands from. +If there are no non-option +arguments, the shell reads commands from the standard input. +The name of the shell (i.e. the contents of $0) +is determined as follows: if the +.Fl c +option is used and there is a non-option argument, it is used as the name; +if commands are being read from a file, the file is used as the name; +otherwise, the basename the shell was called with (i.e. argv[0]) is used. +.Pp +The exit status of the shell is 127 if the command file specified on the +command line could not be opened, or non-zero if a fatal syntax error +occurred during the execution of a script. +In the absence of fatal errors, +the exit status is that of the last command executed, or zero, if no +command is executed. +.Ss Startup files +For the actual location of these files, see +.Sx FILES . +A login shell processes the system profile first. +A privileged shell then processes the suid profile. +A non-privileged login shell processes the user profile next. +A non-privileged interactive shell checks the value of the +.Ev ENV +parameter after subjecting it to parameter, command, arithmetic and tilde +.Pq Sq \*(TI +substitution; if unset or empty, the user mkshrc profile is processed; +otherwise, if a file whose name is the substitution result exists, +it is processed; non-existence is silently ignored. +.Ss Command syntax +The shell begins parsing its input by removing any backslash-newline +combinations, then breaking it into +.Em words . +Words (which are sequences of characters) are delimited by unquoted whitespace +characters (space, tab, and newline) or meta-characters +.Po +.Ql \*(Lt , +.Ql \*(Gt , +.Ql \*(Ba , +.Ql \&; , +.Ql \&( , +.Ql \&) , +and +.Ql & +.Pc . +Aside from delimiting words, spaces and tabs are ignored, while newlines +usually delimit commands. +The meta-characters are used in building the following +.Em tokens : +.Ql \*(Lt , +.Ql \*(Lt& , +.Ql \*(Lt\*(Lt , +.Ql \*(Lt\*(Lt\*(Lt , +.Ql \*(Gt , +.Ql \*(Gt& , +.Ql \*(Gt\*(Gt , +.Ql &\*(Gt , +etc. are used to specify redirections (see +.Sx Input/output redirection +below); +.Ql \*(Ba +is used to create pipelines; +.Ql \*(Ba& +is used to create co-processes (see +.Sx Co-processes +below); +.Ql \&; +is used to separate commands; +.Ql & +is used to create asynchronous pipelines; +.Ql && +and +.Ql \*(Ba\*(Ba +are used to specify conditional execution; +.Ql ;; , +.Ql ;&\& +and +.Ql ;\*(Ba\& +are used in +.Ic case +statements; +.Ql \&(( .. )) +is used in arithmetic expressions; +and lastly, +.Ql \&( .. )\& +is used to create subshells. +.Pp +Whitespace and meta-characters can be quoted individually using a backslash +.Pq Sq \e , +or in groups using double +.Pq Sq \&" +or single +.Pq Sq \*(aq +quotes. +Note that the following characters are also treated specially by the +shell and must be quoted if they are to represent themselves: +.Ql \e , +.Ql \&" , +.Ql \*(aq , +.Ql # , +.Ql $ , +.Ql \` , +.Ql \*(TI , +.Ql { , +.Ql } , +.Ql * , +.Ql \&? , +and +.Ql \&[ . +The first three of these are the above mentioned quoting characters (see +.Sx Quoting +below); +.Ql # , +if used at the beginning of a word, introduces a comment \*(en everything after +the +.Ql # +up to the nearest newline is ignored; +.Ql $ +is used to introduce parameter, command, and arithmetic substitutions (see +.Sx Substitution +below); +.Ql \` +introduces an old-style command substitution (see +.Sx Substitution +below); +.Ql \*(TI +begins a directory expansion (see +.Sx Tilde expansion +below); +.Ql { +and +.Ql } +delimit +.Xr csh 1 Ns -style +alterations (see +.Sx Brace expansion +below); +and finally, +.Ql * , +.Ql \&? , +and +.Ql \&[ +are used in file name generation (see +.Sx File name patterns +below). +.Pp +As words and tokens are parsed, the shell builds commands, of which there +are two basic types: +.Em simple-commands , +typically programmes that are executed, and +.Em compound-commands , +such as +.Ic for +and +.Ic if +statements, grouping constructs, and function definitions. +.Pp +A simple-command consists of some combination of parameter assignments +(see +.Sx Parameters +below), +input/output redirections (see +.Sx Input/output redirections +below), +and command words; the only restriction is that parameter assignments come +before any command words. +The command words, if any, define the command +that is to be executed and its arguments. +The command may be a shell built-in command, a function, +or an external command +(i.e. a separate executable file that is located using the +.Ev PATH +parameter; see +.Sx Command execution +below). +Note that all command constructs have an exit status: for external commands, +this is related to the status returned by +.Xr wait 2 +(if the command could not be found, the exit status is 127; if it could not +be executed, the exit status is 126); the exit status of other command +constructs (built-in commands, functions, compound-commands, pipelines, lists, +etc.) are all well-defined and are described where the construct is +described. +The exit status of a command consisting only of parameter +assignments is that of the last command substitution performed during the +parameter assignment or 0 if there were no command substitutions. +.Pp +Commands can be chained together using the +.Ql \*(Ba +token to form pipelines, in which the standard output of each command but the +last is piped (see +.Xr pipe 2 ) +to the standard input of the following command. +The exit status of a pipeline is that of its last command. +All commands of a pipeline are executed in separate subshells; +this is allowed by POSIX but differs from both variants of +.At +.Nm ksh , +where all but the last command were executed in subshells; see the +.Ic read +builtin's description for implications and workarounds. +A pipeline may be prefixed by the +.Ql \&! +reserved word which causes the exit status of the pipeline to be logically +complemented: if the original status was 0, the complemented status will be 1; +if the original status was not 0, the complemented status will be 0. +.Pp +.Em Lists +of commands can be created by separating pipelines by any of the following +tokens: +.Ql && , +.Ql \*(Ba\*(Ba , +.Ql & , +.Ql \*(Ba& , +and +.Ql \&; . +The first two are for conditional execution: +.Dq Ar cmd1 No && Ar cmd2 +executes +.Ar cmd2 +only if the exit status of +.Ar cmd1 +is zero; +.Ql \*(Ba\*(Ba +is the opposite \*(en +.Ar cmd2 +is executed only if the exit status of +.Ar cmd1 +is non-zero. +.Ql && +and +.Ql \*(Ba\*(Ba +have equal precedence which is higher than that of +.Ql & , +.Ql \*(Ba& , +and +.Ql \&; , +which also have equal precedence. +Note that the +.Ql && +and +.Ql \*(Ba\*(Ba +operators are +.Qq left-associative . +For example, both of these commands will print only +.Qq bar : +.Bd -literal -offset indent +$ false && echo foo \*(Ba\*(Ba echo bar +$ true \*(Ba\*(Ba echo foo && echo bar +.Ed +.Pp +The +.Ql & +token causes the preceding command to be executed asynchronously; that is, +the shell starts the command but does not wait for it to complete (the shell +does keep track of the status of asynchronous commands; see +.Sx Job control +below). +When an asynchronous command is started when job control is disabled +(i.e. in most scripts), the command is started with signals +.Dv SIGINT +and +.Dv SIGQUIT +ignored and with input redirected from +.Pa /dev/null +(however, redirections specified in the asynchronous command have precedence). +The +.Ql \*(Ba& +operator starts a co-process which is a special kind of asynchronous process +(see +.Sx Co-processes +below). +Note that a command must follow the +.Ql && +and +.Ql \*(Ba\*(Ba +operators, while it need not follow +.Ql & , +.Ql \*(Ba& , +or +.Ql \&; . +The exit status of a list is that of the last command executed, with the +exception of asynchronous lists, for which the exit status is 0. +.Pp +Compound commands are created using the following reserved words. +These words +are only recognised if they are unquoted and if they are used as the first +word of a command (i.e. they can't be preceded by parameter assignments or +redirections): +.Bd -literal -offset indent +case else function then ! ( +do esac if time [[ (( +done fi in until { +elif for select while } +.Ed +.Pp +In the following compound command descriptions, command lists (denoted as +.Em list ) +that are followed by reserved words must end with a semicolon, a newline, or +a (syntactically correct) reserved word. +For example, the following are all valid: +.Bd -literal -offset indent +$ { echo foo; echo bar; } +$ { echo foo; echo bar\*(Ltnewline\*(Gt} +$ { { echo foo; echo bar; } } +.Ed +.Pp +This is not valid: +.Pp +.Dl $ { echo foo; echo bar } +.Bl -tag -width 4n +.It Pq Ar list +Execute +.Ar list +in a subshell. +There is no implicit way to pass environment changes from a +subshell back to its parent. +.It { Ar list ; No } +Compound construct; +.Ar list +is executed, but not in a subshell. +Note that +.Ql { +and +.Ql } +are reserved words, not meta-characters. +.It Xo case Ar word No in +.Oo Op \&( +.Ar pattern +.Op \*(Ba Ar pat +.No ... Ns ) +.Ar list +.Op ;; \*(Ba ;&\& \*(Ba ;\*(Ba\ \& +.Oc ... esac +.Xc +The +.Ic case +statement attempts to match +.Ar word +against a specified +.Ar pattern ; +the +.Ar list +associated with the first successfully matched pattern is executed. +Patterns used in +.Ic case +statements are the same as those used for file name patterns except that the +restrictions regarding +.Ql \&. +and +.Ql / +are dropped. +Note that any unquoted space before and after a pattern is +stripped; any space within a pattern must be quoted. +Both the word and the +patterns are subject to parameter, command, and arithmetic substitution, as +well as tilde substitution. +.Pp +For historical reasons, open and close braces may be used instead of +.Ic in +and +.Ic esac +e.g.\& +.Ic case $foo { *) echo bar;; } . +.Pp +The list terminators are: +.Bl -tag -width 4n +.It Ql ;; +Terminate after the list. +.It Ql ;&\& +Fall through into the next list. +.It Ql ;\*(Ba\& +Evaluate the remaining pattern-list tuples. +.El +.Pp +The exit status of a +.Ic case +statement is that of the executed +.Ar list ; +if no +.Ar list +is executed, the exit status is zero. +.It Xo for Ar name +.Oo in Ar word No ... Oc ; +.No do Ar list ; No done +.Xc +For each +.Ar word +in the specified word list, the parameter +.Ar name +is set to the word and +.Ar list +is executed. +If +.Ic in +is not used to specify a word list, the positional parameters +($1, $2, etc.)\& +are used instead. +For historical reasons, open and close braces may be used instead of +.Ic do +and +.Ic done +e.g.\& +.Ic for i; { echo $i; } . +The exit status of a +.Ic for +statement is the last exit status of +.Ar list ; +if +.Ar list +is never executed, the exit status is zero. +.It Xo if Ar list ; +.No then Ar list ; +.Oo elif Ar list ; +.No then Ar list ; Oc +.No ... +.Oo else Ar list ; Oc +.No fi +.Xc +If the exit status of the first +.Ar list +is zero, the second +.Ar list +is executed; otherwise, the +.Ar list +following the +.Ic elif , +if any, is executed with similar consequences. +If all the lists following the +.Ic if +and +.Ic elif Ns s +fail (i.e. exit with non-zero status), the +.Ar list +following the +.Ic else +is executed. +The exit status of an +.Ic if +statement is that of non-conditional +.Ar list +that is executed; if no non-conditional +.Ar list +is executed, the exit status is zero. +.It Xo select Ar name +.Oo in Ar word No ... Oc ; +.No do Ar list ; No done +.Xc +The +.Ic select +statement provides an automatic method of presenting the user with a menu and +selecting from it. +An enumerated list of the specified +.Ar word Ns (s) +is printed on standard error, followed by a prompt +.Po +.Ev PS3: normally +.Sq #?\ \& +.Pc . +A number corresponding to one of the enumerated words is then read from +standard input, +.Ar name +is set to the selected word (or unset if the selection is not valid), +.Ev REPLY +is set to what was read (leading/trailing space is stripped), and +.Ar list +is executed. +If a blank line (i.e. zero or more +.Ev IFS +octets) is entered, the menu is reprinted without executing +.Ar list . +.Pp +When +.Ar list +completes, the enumerated list is printed if +.Ev REPLY +is +.Dv NULL , +the prompt is printed, and so on. +This process continues until an end-of-file +is read, an interrupt is received, or a +.Ic break +statement is executed inside the loop. +If +.Dq in word ... +is omitted, the positional parameters are used +(i.e. $1, $2, etc.). +For historical reasons, open and close braces may be used instead of +.Ic do +and +.Ic done +e.g.\& +.Ic select i; { echo $i; } . +The exit status of a +.Ic select +statement is zero if a +.Ic break +statement is used to exit the loop, non-zero otherwise. +.It Xo until Ar list ; +.No do Ar list ; +.No done +.Xc +This works like +.Ic while , +except that the body is executed only while the exit status of the first +.Ar list +is non-zero. +.It Xo while Ar list ; +.No do Ar list ; +.No done +.Xc +A +.Ic while +is a pre-checked loop. +Its body is executed as often as the exit status of the first +.Ar list +is zero. +The exit status of a +.Ic while +statement is the last exit status of the +.Ar list +in the body of the loop; if the body is not executed, the exit status is zero. +.It Xo function Ar name +.No { Ar list ; No } +.Xc +Defines the function +.Ar name +(see +.Sx Functions +below). +Note that redirections specified after a function definition are +performed whenever the function is executed, not when the function definition +is executed. +.It Ar name Ns \&() Ar command +Mostly the same as +.Ic function +(see +.Sx Functions +below). +Whitespace (space or tab) after +.Ar name +will be ignored most of the time. +.It Xo function Ar name Ns \&() +.No { Ar list ; No } +.Xc +The same as +.Ar name Ns \&() +.Pq Nm bash Ns ism . +The +.Ic function +keyword is ignored. +.It Xo Ic time Op Fl p +.Op Ar pipeline +.Xc +The +.Sx Command execution +section describes the +.Ic time +reserved word. +.It \&(( Ar expression No )) +The arithmetic expression +.Ar expression +is evaluated; equivalent to +.Dq let expression +(see +.Sx Arithmetic expressions +and the +.Ic let +command, below). +.It Bq Bq Ar \ \&expression\ \& +Similar to the +.Ic test +and +.Ic \&[ ... \&] +commands (described later), with the following exceptions: +.Bl -bullet +.It +Field splitting and file name generation are not performed on arguments. +.It +The +.Fl a +.Pq AND +and +.Fl o +.Pq OR +operators are replaced with +.Ql && +and +.Ql \*(Ba\*(Ba , +respectively. +.It +Operators (e.g.\& +.Sq Fl f , +.Sq = , +.Sq \&! ) +must be unquoted. +.It +Parameter, command, and arithmetic substitutions are performed as expressions +are evaluated and lazy expression evaluation is used for the +.Ql && +and +.Ql \*(Ba\*(Ba +operators. +This means that in the following statement, +.Ic $(\*(Ltfoo) +is evaluated if and only if the file +.Pa foo +exists and is readable: +.Bd -literal -offset indent +$ [[ \-r foo && $(\*(Ltfoo) = b*r ]] +.Ed +.It +The second operand of the +.Sq != +and +.Sq = +expressions are patterns (e.g. the comparison +.Ic \&[[ foobar = f*r ]] +succeeds). +This even works indirectly: +.Bd -literal -offset indent +$ bar=foobar; baz=\*(aqf*r\*(aq +$ [[ $bar = $baz ]]; echo $? +$ [[ $bar = "$baz" ]]; echo $? +.Ed +.Pp +Perhaps surprisingly, the first comparison succeeds, +whereas the second doesn't. +.El +.El +.Ss Quoting +Quoting is used to prevent the shell from treating characters or words +specially. +There are three methods of quoting. +First, +.Ql \e +quotes the following character, unless it is at the end of a line, in which +case both the +.Ql \e +and the newline are stripped. +Second, a single quote +.Pq Sq \*(aq +quotes everything up to the next single quote (this may span lines). +Third, a double quote +.Pq Sq \&" +quotes all characters, except +.Ql $ , +.Ql \` +and +.Ql \e , +up to the next unquoted double quote. +.Ql $ +and +.Ql \` +inside double quotes have their usual meaning (i.e. parameter, command, or +arithmetic substitution) except no field splitting is carried out on the +results of double-quoted substitutions. +If a +.Ql \e +inside a double-quoted string is followed by +.Ql \e , +.Ql $ , +.Ql \` , +or +.Ql \&" , +it is replaced by the second character; if it is followed by a newline, both +the +.Ql \e +and the newline are stripped; otherwise, both the +.Ql \e +and the character following are unchanged. +.Pp +If a single-quoted string is preceded by an unquoted +.Ql $ , +C style backslash expansion (see below) is applied (even single quote +characters inside can be escaped and do not terminate the string then); +the expanded result is treated as any other single-quoted string. +If a double-quoted string is preceded by an unquoted +.Ql $ , +the latter is ignored. +.Ss Backslash expansion +In places where backslashes are expanded, certain C and +.At +.Nm ksh +or GNU +.Nm bash +style escapes are translated. +These include +.Ql \ea , +.Ql \eb , +.Ql \ef , +.Ql \en , +.Ql \er , +.Ql \et , +.Ql \eU######## , +.Ql \eu#### , +and +.Ql \ev . +For +.Ql \eU######## +and +.Ql \eu#### , +.Dq # +means a hexadecimal digit, of thich there may be none up to four or eight; +these escapes translate a Unicode codepoint to UTF-8. +Furthermore, +.Ql \eE +and +.Ql \ee +expand to the escape character. +.Pp +In the +.Ic print +builtin mode, +.Ql \e" , +.Ql \e\*(aq , +and +.Ql \e? +are explicitly excluded; +octal sequences must have the none up to three octal digits +.Dq # +prefixed with the digit zero +.Pq Ql \e0### ; +hexadecimal sequences +.Ql \ex## +are limited to none up to two hexadecimal digits +.Dq # ; +both octal and hexadecimal sequences convert to raw octets; +.Ql \e# , +where # is none of the above, translates to \e# (backslashes are retained). +.Pp +Backslash expansion in the C style mode slightly differs: octal sequences +.Ql \e### +must have no digit zero prefixing the one up to three octal digits +.Dq # +and yield raw octets; hexadecimal sequences +.Ql \ex#* +greedily eat up as many hexadecimal digits +.Dq # +as they can and terminate with the first non-hexadecimal digit; +these translate a Unicode codepoint to UTF-8. +The sequence +.Ql \ec# , +where +.Dq # +is any octet, translates to Ctrl-# (which basically means, +.Ql \ec? +becomes DEL, everything else is bitwise ANDed with 0x1F). +Finally, +.Ql \e# , +where # is none of the above, translates to # (has the backslash trimmed), +even if it is a newline. +.Ss Aliases +There are two types of aliases: normal command aliases and tracked aliases. +Command aliases are normally used as a short hand for a long or often used +command. +The shell expands command aliases (i.e. substitutes the alias name +for its value) when it reads the first word of a command. +An expanded alias is re-processed to check for more aliases. +If a command alias ends in a +space or tab, the following word is also checked for alias expansion. +The alias expansion process stops when a word that is not an alias is found, +when a quoted word is found, or when an alias word that is currently being +expanded is found. +.Pp +The following command aliases are defined automatically by the shell: +.Bd -literal -offset indent +autoload=\*(aqtypeset \-fu\*(aq +functions=\*(aqtypeset \-f\*(aq +hash=\*(aqalias \-t\*(aq +history=\*(aqfc \-l\*(aq +integer=\*(aqtypeset \-i\*(aq +local=\*(aqtypeset\*(aq +login=\*(aqexec login\*(aq +nameref=\*(aqtypeset \-n\*(aq +nohup=\*(aqnohup \*(aq +r=\*(aqfc \-e \-\*(aq +stop=\*(aqkill \-STOP\*(aq +suspend=\*(aqkill \-STOP $$\*(aq +type=\*(aqwhence \-v\*(aq +.Ed +.Pp +Tracked aliases allow the shell to remember where it found a particular +command. +The first time the shell does a path search for a command that is +marked as a tracked alias, it saves the full path of the command. +The next +time the command is executed, the shell checks the saved path to see that it +is still valid, and if so, avoids repeating the path search. +Tracked aliases can be listed and created using +.Ic alias \-t . +Note that changing the +.Ev PATH +parameter clears the saved paths for all tracked aliases. +If the +.Ic trackall +option is set (i.e.\& +.Ic set \-o Ic trackall +or +.Ic set \-h ) , +the shell tracks all commands. +This option is set automatically for non-interactive shells. +For interactive shells, only the following commands are +automatically tracked: +.Xr cat 1 , +.Xr cc 1 , +.Xr chmod 1 , +.Xr cp 1 , +.Xr date 1 , +.Xr ed 1 , +.Xr emacs 1 , +.Xr grep 1 , +.Xr ls 1 , +.Xr make 1 , +.Xr mv 1 , +.Xr pr 1 , +.Xr rm 1 , +.Xr sed 1 , +.Xr sh 1 , +.Xr vi 1 , +and +.Xr who 1 . +.Ss Substitution +The first step the shell takes in executing a simple-command is to perform +substitutions on the words of the command. +There are three kinds of +substitution: parameter, command, and arithmetic. +Parameter substitutions, +which are described in detail in the next section, take the form +.Pf $ Ns Ar name +or +.Pf ${ Ns Ar ... Ns } ; +command substitutions take the form +.Pf $( Ns Ar command Ns \&) +or (deprecated) +.Pf \` Ns Ar command Ns \` ; +and arithmetic substitutions take the form +.Pf $(( Ns Ar expression Ns )) . +.Pp +If a substitution appears outside of double quotes, the results of the +substitution are generally subject to word or field splitting according to +the current value of the +.Ev IFS +parameter. +The +.Ev IFS +parameter specifies a list of octets which are used to break a string up +into several words; any octets from the set space, tab, and newline that +appear in the +.Ev IFS +octets are called +.Dq IFS whitespace . +Sequences of one or more +.Ev IFS +whitespace octets, in combination with zero or one +.Pf non- Ev IFS +whitespace octets, delimit a field. +As a special case, leading and trailing +.Ev IFS +whitespace and trailing +.Ev IFS +non-whitespace are stripped (i.e. no leading or trailing empty field +is created by it); leading +.Pf non- Ev IFS +whitespace does create an empty field. +.Pp +Example: If +.Ev IFS +is set to +.Dq \*(Ltspace\*(Gt: , +and VAR is set to +.Dq \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D , +the substitution for $VAR results in four fields: +.Sq A , +.Sq B , +.Sq +(an empty field), +and +.Sq D . +Note that if the +.Ev IFS +parameter is set to the +.Dv NULL +string, no field splitting is done; if the parameter is unset, the default +value of space, tab, and newline is used. +.Pp +Also, note that the field splitting applies only to the immediate result of +the substitution. +Using the previous example, the substitution for $VAR:E +results in the fields: +.Sq A , +.Sq B , +.Sq , +and +.Sq D:E , +not +.Sq A , +.Sq B , +.Sq , +.Sq D , +and +.Sq E . +This behavior is POSIX compliant, but incompatible with some other shell +implementations which do field splitting on the word which contained the +substitution or use +.Dv IFS +as a general whitespace delimiter. +.Pp +The results of substitution are, unless otherwise specified, also subject to +brace expansion and file name expansion (see the relevant sections below). +.Pp +A command substitution is replaced by the output generated by the specified +command which is run in a subshell. +For +.Pf $( Ns Ar command Ns \&) +substitutions, normal quoting rules are used when +.Ar command +is parsed; however, for the deprecated +.Pf \` Ns Ar command Ns \` +form, a +.Ql \e +followed by any of +.Ql $ , +.Ql \` , +or +.Ql \e +is stripped (a +.Ql \e +followed by any other character is unchanged). +As a special case in command substitutions, a command of the form +.Pf \*(Lt Ar file +is interpreted to mean substitute the contents of +.Ar file . +Note that +.Ic $(\*(Ltfoo) +has the same effect as +.Ic $(cat foo) . +.Pp +Note that some shells do not use a recursive parser for command substitutions, +leading to failure for certain constructs; to be portable, use as workaround +.Ql x=$(cat) \*(Lt\*(Lt"EOF" +(or the newline-keeping +.Ql x=\*(Lt\*(Lt"EOF" +extension) instead to merely slurp the string. +.St -p1003.1 +recommends to use case statements of the form +.Ql "x=$(case $foo in (bar) echo $bar ;; (*) echo $baz ;; esac)" +instead, which would work but not serve as example for this portability issue. +.Bd -literal -offset indent +x=$(case $foo in bar) echo $bar ;; *) echo $baz ;; esac) +# above fails to parse on old shells; below is the workaround +x=$(eval $(cat)) \*(Lt\*(Lt"EOF" +case $foo in bar) echo $bar ;; *) echo $baz ;; esac +EOF +.Ed +.Pp +Arithmetic substitutions are replaced by the value of the specified expression. +For example, the command +.Ic print $((2+3*4)) +displays 14. +See +.Sx Arithmetic expressions +for a description of an expression. +.Ss Parameters +Parameters are shell variables; they can be assigned values and their values +can be accessed using a parameter substitution. +A parameter name is either one +of the special single punctuation or digit character parameters described +below, or a letter followed by zero or more letters or digits +.Po +.Ql _ +counts as a letter +.Pc . +The latter form can be treated as arrays by appending an array index of the +form +.Op Ar expr +where +.Ar expr +is an arithmetic expression. +Array indices in +.Nm +are limited to the range 0 through 4294967295, inclusive. +That is, they are a 32-bit unsigned integer. +.Pp +Parameter substitutions take the form +.Pf $ Ns Ar name , +.Pf ${ Ns Ar name Ns } , +or +.Sm off +.Pf ${ Ar name Oo Ar expr Oc } +.Sm on +where +.Ar name +is a parameter name. +Substitution of all array elements with +.Pf ${ Ns Ar name Ns \&[*]} +and +.Pf ${ Ns Ar name Ns \&[@]} +works equivalent to $* and $@ for positional parameters. +If substitution is performed on a parameter +(or an array parameter element) +that is not set, a null string is substituted unless the +.Ic nounset +option +.Po +.Ic set Fl o Ic nounset +or +.Ic set Fl u +.Pc +is set, in which case an error occurs. +.Pp +Parameters can be assigned values in a number of ways. +First, the shell implicitly sets some parameters like +.Ql # , +.Ql PWD , +and +.Ql $ ; +this is the only way the special single character parameters are set. +Second, parameters are imported from the shell's environment at startup. +Third, parameters can be assigned values on the command line: for example, +.Ic FOO=bar +sets the parameter +.Dq FOO +to +.Dq bar ; +multiple parameter assignments can be given on a single command line and they +can be followed by a simple-command, in which case the assignments are in +effect only for the duration of the command (such assignments are also +exported; see below for the implications of this). +Note that both the parameter name and the +.Ql = +must be unquoted for the shell to recognise a parameter assignment. +The construct +.Ic FOO+=baz +is also recognised; the old and new values are immediately concatenated. +The fourth way of setting a parameter is with the +.Ic export , +.Ic global , +.Ic readonly , +and +.Ic typeset +commands; see their descriptions in the +.Sx Command execution +section. +Fifth, +.Ic for +and +.Ic select +loops set parameters as well as the +.Ic getopts , +.Ic read , +and +.Ic set \-A +commands. +Lastly, parameters can be assigned values using assignment operators +inside arithmetic expressions (see +.Sx Arithmetic expressions +below) or using the +.Sm off +.Pf ${ Ar name No = Ar value No } +.Sm on +form of the parameter substitution (see below). +.Pp +Parameters with the export attribute (set using the +.Ic export +or +.Ic typeset Fl x +commands, or by parameter assignments followed by simple commands) are put in +the environment (see +.Xr environ 7 ) +of commands run by the shell as +.Ar name Ns = Ns Ar value +pairs. +The order in which parameters appear in the environment of a command is +unspecified. +When the shell starts up, it extracts parameters and their values +from its environment and automatically sets the export attribute for those +parameters. +.Pp +Modifiers can be applied to the +.Pf ${ Ns Ar name Ns } +form of parameter substitution: +.Bl -tag -width Ds +.Sm off +.It ${ Ar name No :\- Ar word No } +.Sm on +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is substituted. +.Sm off +.It ${ Ar name No :+ Ar word No } +.Sm on +If +.Ar name +is set and not +.Dv NULL , +.Ar word +is substituted; otherwise, nothing is substituted. +.Sm off +.It ${ Ar name No := Ar word No } +.Sm on +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, it is assigned +.Ar word +and the resulting value of +.Ar name +is substituted. +.Sm off +.It ${ Ar name No :? Ar word No } +.Sm on +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is printed on standard error (preceded by +.Ar name : ) +and an error occurs (normally causing termination of a shell script, function, +or script sourced using the +.Sq \&. +built-in). +If +.Ar word +is omitted, the string +.Dq parameter null or not set +is used instead. +Currently a bug, if +.Ar word +is a variable which expands to the null string, the +error message is also printed. +.El +.Pp +Note that, for all of the above, +.Ar word +is actually considered quoted, and special parsing rules apply. +The parsing rules also differ on whether the expression is double-quoted: +.Ar word +then uses double-quoting rules, except for the double quote itself +.Pq Sq \&" +and the closing brace, which, if backslash escaped, gets quote removal applied. +.Pp +In the above modifiers, the +.Ql \&: +can be omitted, in which case the conditions only depend on +.Ar name +being set (as opposed to set and not +.Dv NULL ) . +If +.Ar word +is needed, parameter, command, arithmetic, and tilde substitution are performed +on it; if +.Ar word +is not needed, it is not evaluated. +.Pp +The following forms of parameter substitution can also be used (if +.Ar name +is an array, its element #0 will be substituted in a scalar context): +.Pp +.Bl -tag -width Ds -compact +.It Pf ${# Ns Ar name Ns \&} +The number of positional parameters if +.Ar name +is +.Ql * , +.Ql @ , +or not specified; otherwise the length +.Pq in characters +of the string value of parameter +.Ar name . +.Pp +.It Pf ${# Ns Ar name Ns \&[*]} +.It Pf ${# Ns Ar name Ns \&[@]} +The number of elements in the array +.Ar name . +.Pp +.It Pf ${% Ns Ar name Ns \&} +The width +.Pq in screen columns +of the string value of parameter +.Ar name , +or \-1 if +.Pf ${ Ns Ar name Ns } +contains a control character. +.Pp +.It Pf ${! Ns Ar name Ns } +The name of the variable referred to by +.Ar name . +This will be +.Ar name +except when +.Ar name +is a name reference (bound variable), created by the +.Ic nameref +command (which is an alias for +.Ic typeset Fl n ) . +.Pp +.It Pf ${! Ns Ar name Ns \&[*]} +.It Pf ${! Ns Ar name Ns \&[@]} +The names of indices (keys) in the array +.Ar name . +.Pp +.Sm off +.It Xo +.Pf ${ Ar name +.Pf # Ar pattern No } +.Xc +.It Xo +.Pf ${ Ar name +.Pf ## Ar pattern No } +.Xc +.Sm on +If +.Ar pattern +matches the beginning of the value of parameter +.Ar name , +the matched text is deleted from the result of substitution. +A single +.Ql # +results in the shortest match, and two +of them result in the longest match. +Cannot be applied to a vector +.Pq ${*} or ${@} or ${array[*]} or ${array[@]} . +.Pp +.Sm off +.It Xo +.Pf ${ Ar name +.Pf % Ar pattern No } +.Xc +.It Xo +.Pf ${ Ar name +.Pf %% Ar pattern No } +.Xc +.Sm on +Like ${..#..} substitution, but it deletes from the end of the value. +Cannot be applied to a vector. +.Pp +.Sm off +.It Xo +.Pf ${ Ar name +.Pf / Ar pattern / Ar string No } +.Xc +.It Xo +.Pf ${ Ar name +.Pf // Ar pattern / Ar string No } +.Xc +.Sm on +Like ${..#..} substitution, but it replaces the longest match of +.Ar pattern , +anchored anywhere in the value, with +.Ar string . +If +.Ar pattern +begins with +.Ql # , +it is anchored at the beginning of the value; if it begins with +.Ql % , +it is anchored at the end. +A single +.Ql / +replaces the first occurence of the search +.Ar pattern , +and two of them replace all occurences. +If +.Pf / Ar string +is omitted, the +.Ar pattern +is replaced by the empty string, i.e. deleted. +Cannot be applied to a vector. +Inefficiently implemented. +.Pp +.Sm off +.It Xo +.Pf ${ Ar name : Ns Ar pos +.Pf : Ns Ar len Ns } +.Xc +.Sm on +The first +.Ar len +characters of +.Ar name , +starting at position +.Ar pos , +are substituted. +Both +.Ar pos +and +.Pf : Ns Ar len +are optional. +If +.Ar pos +is negative, counting starts at the end of the string; if it +is omitted, it defaults to 0. +If +.Ar len +is omitted or greater than the length of the remaining string, +all of it is substituted. +Both +.Ar pos +and +.Ar len +are evaluated as arithmetic expressions. +Currently, +.Ar pos +must start with a space, opening parenthesis or digit to be recognised. +Cannot be applied to a vector. +.Pp +.It Pf ${ Ns Ar name Ns @#} +The internal hash of the expansion of +.Ar name . +At the moment, this is NZAT (a never-zero 32-bit hash based on +Bob Jenkins' one-at-a-time hash), but this is not set. +This is the hash the shell uses internally for its associative arrays. +.El +.Pp +Note that +.Ar pattern +may need extended globbing pattern +.Pq @(...) , +single +.Pq \&\*(aq...\&\*(aq +or double +.Pq \&"...\&" +quote escaping unless +.Fl o Ic sh +is set. +.Pp +The following special parameters are implicitly set by the shell and cannot be +set directly using assignments: +.Bl -tag -width "1 .. 9" +.It Ev \&! +Process ID of the last background process started. +If no background processes have been started, the parameter is not set. +.It Ev \&# +The number of positional parameters ($1, $2, etc.). +.It Ev \&$ +The PID of the shell, or the PID of the original shell if it is a subshell. +Do +.Em NOT +use this mechanism for generating temporary file names; see +.Xr mktemp 1 +instead. +.It Ev \- +The concatenation of the current single letter options (see the +.Ic set +command below for a list of options). +.It Ev \&? +The exit status of the last non-asynchronous command executed. +If the last command was killed by a signal, +.Ic $?\& +is set to 128 plus the signal number. +.It Ev 0 +The name of the shell, determined as follows: +the first argument to +.Nm +if it was invoked with the +.Fl c +option and arguments were given; otherwise the +.Ar file +argument, if it was supplied; +or else the basename the shell was invoked with (i.e.\& +.Li argv[0] ) . +.Ev $0 +is also set to the name of the current script or +the name of the current function, if it was defined with the +.Ic function +keyword (i.e. a Korn shell style function). +.It Ev 1 No .. Ev 9 +The first nine positional parameters that were supplied to the shell, function, +or script sourced using the +.Sq \&. +built-in. +Further positional parameters may be accessed using +.Pf ${ Ar number Ns } . +.It Ev * +All positional parameters (except 0), i.e. $1, $2, $3, ... +.br +If used +outside of double quotes, parameters are separate words (which are subjected +to word splitting); if used within double quotes, parameters are separated +by the first character of the +.Ev IFS +parameter (or the empty string if +.Ev IFS +is +.Dv NULL ) . +.It Ev @ +Same as +.Ic $* , +unless it is used inside double quotes, in which case a separate word is +generated for each positional parameter. +If there are no positional parameters, no word is generated. +.Ic $@ +can be used to access arguments, verbatim, without losing +.Dv NULL +arguments or splitting arguments with spaces. +.El +.Pp +The following parameters are set and/or used by the shell: +.Bl -tag -width "KSH_VERSION" +.It Ev _ +.Pq underscore +When an external command is executed by the shell, this parameter is set in the +environment of the new process to the path of the executed command. +In interactive use, this parameter is also set in the parent shell to the last +word of the previous command. +.It Ev CDPATH +Search path for the +.Ic cd +built-in command. +It works the same way as +.Ev PATH +for those directories not beginning with +.Ql / +in +.Ic cd +commands. +Note that if +.Ev CDPATH +is set and does not contain +.Sq \&. +or contains an empty path, the current directory is not searched. +Also, the +.Ic cd +built-in command will display the resulting directory when a match is found +in any search path other than the empty path. +.It Ev COLUMNS +Set to the number of columns on the terminal or window. +Always set, defaults to 80, unless the +value as reported by +.Xr stty 1 +is non-zero and sane enough; similar for +.Ev LINES . +This parameter is used by the interactive line editing modes, and by the +.Ic select , +.Ic set \-o , +and +.Ic kill \-l +commands to format information columns. +.It Ev ENV +If this parameter is found to be set after any profile files are executed, the +expanded value is used as a shell startup file. +It typically contains function and alias definitions. +.It Ev ERRNO +Integer value of the shell's +.Va errno +variable. +It indicates the reason the last system call failed. +Not yet implemented. +.It Ev EXECSHELL +If set, this parameter is assumed to contain the shell that is to be used to +execute commands that +.Xr execve 2 +fails to execute and which do not start with a +.Dq #! Ns Ar shell +sequence. +.It Ev FCEDIT +The editor used by the +.Ic fc +command (see below). +.It Ev FPATH +Like +.Ev PATH , +but used when an undefined function is executed to locate the file defining the +function. +It is also searched when a command can't be found using +.Ev PATH . +See +.Sx Functions +below for more information. +.It Ev HISTFILE +The name of the file used to store command history. +When assigned to, history is loaded from the specified file. +Also, several invocations of the shell will share history if their +.Ev HISTFILE +parameters all point to the same file. +.Pp +.Sy Note : +If +.Ev HISTFILE +isn't set, no history file is used. +This is different from +.At +.Nm ksh . +.It Ev HISTSIZE +The number of commands normally stored for history. +The default is 500. +.It Ev HOME +The default directory for the +.Ic cd +command and the value substituted for an unqualified +.Ic \*(TI +(see +.Sx Tilde expansion +below). +.It Ev IFS +Internal field separator, used during substitution and by the +.Ic read +command, to split values into distinct arguments; normally set to space, tab, +and newline. +See +.Sx Substitution +above for details. +.Pp +.Sy Note : +This parameter is not imported from the environment when the shell is +started. +.It Ev KSHEGID +The effective group id of the shell. +.It Ev KSHGID +The real group id of the shell. +.It Ev KSHUID +The real user id of the shell. +.It Ev KSH_VERSION +The name and version of the shell (read-only). +See also the version commands in +.Sx Emacs editing mode +and +.Sx Vi editing mode +sections, below. +.It Ev LINENO +The line number of the function or shell script that is currently being +executed. +.It Ev LINES +Set to the number of lines on the terminal or window. +Always set, defaults to 24. +.It Ev OLDPWD +The previous working directory. +Unset if +.Ic cd +has not successfully changed directories since the shell started, or if the +shell doesn't know where it is. +.It Ev OPTARG +When using +.Ic getopts , +it contains the argument for a parsed option, if it requires one. +.It Ev OPTIND +The index of the next argument to be processed when using +.Ic getopts . +Assigning 1 to this parameter causes +.Ic getopts +to process arguments from the beginning the next time it is invoked. +.It Ev PATH +A colon separated list of directories that are searched when looking for +commands and files sourced using the +.Sq \&. +command (see below). +An empty string resulting from a leading or trailing +colon, or two adjacent colons, is treated as a +.Sq \&. +(the current directory). +.It Ev PGRP +The process ID of the shell's process group leader. +.It Ev PIPESTATUS +An array containing the errorlevel (exit status) codes, +one by one, of the last pipeline run in the foreground. +.It Ev PPID +The process ID of the shell's parent. +.It Ev PS1 +The primary prompt for interactive shells. +Parameter, command, and arithmetic +substitutions are performed, and +.Ql \&! +is replaced with the current command number (see the +.Ic fc +command below). +A literal +.Ql \&! +can be put in the prompt by placing +.Ql !! +in +.Ev PS1 . +.Pp +The default prompt is +.Sq $\ \& +for non-root users, +.Sq #\ \& +for root. +If +.Nm +is invoked by root and +.Ev PS1 +does not contain a +.Sq # +character, the default value will be used even if +.Ev PS1 +already exists in the environment. +.Pp +The +.Nm +distribution comes with a sample +.Pa dot.mkshrc +containing a sophisticated example, but you might like the following one +(note that ${HOSTNAME:=$(hostname)} and the +root-vs-user distinguishing clause are (in this example) executed at +.Ev PS1 +assignment time, while the $USER and $PWD are escaped +and thus will be evaluated each time a prompt is displayed): +.Bd -literal +PS1=\*(aq${USER:=$(id \-un)}\*(aq"@${HOSTNAME:=$(hostname)}:\e$PWD $( + if (( USER_ID )); then print \e$; else print \e#; fi) " +.Ed +.Pp +Note that since the command-line editors try to figure out how long the prompt +is (so they know how far it is to the edge of the screen), escape codes in +the prompt tend to mess things up. +You can tell the shell not to count certain +sequences (such as escape codes) by prefixing your prompt with a +character (such as Ctrl-A) followed by a carriage return and then delimiting +the escape codes with this character. +Any occurences of that character in the prompt are not printed. +By the way, don't blame me for +this hack; it's derived from the original +.Xr ksh88 1 , +which did print the delimiter character so you were out of luck +if you did not have any non-printing characters. +.Pp +Since Backslashes and other special characters may be +interpreted by the shell, to set +.Ev PS1 +either escape the backslash itself, +or use double quotes. +The latter is more practical. +This is a more complex example, +avoiding to directly enter special characters (for example with +.Ic \*(haV +in the emacs editing mode), +which embeds the current working directory, +in reverse video +.Pq colour would work, too , +in the prompt string: +.Bd -literal -offset indent +x=$(print \e\e001) +PS1="$x$(print \e\er)$x$(tput smso)$x\e$PWD$x$(tput rmso)$x\*(Gt " +.Ed +.Pp +Due to pressure from David G. Korn, +.Nm +now also supports the following form: +.Bd -literal -offset indent +PS1=$'\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt ' +.Ed +.It Ev PS2 +Secondary prompt string, by default +.Sq \*(Gt\ \& , +used when more input is needed to complete a command. +.It Ev PS3 +Prompt used by the +.Ic select +statement when reading a menu selection. +The default is +.Sq #?\ \& . +.It Ev PS4 +Used to prefix commands that are printed during execution tracing (see the +.Ic set Fl x +command below). +Parameter, command, and arithmetic substitutions are performed +before it is printed. +The default is +.Sq +\ \& . +.It Ev PWD +The current working directory. +May be unset or +.Dv NULL +if the shell doesn't know where it is. +.It Ev RANDOM +Each time +.Ev RANDOM +is referenced, it is assigned a number between 0 and 32767 from +a Linear Congruential PRNG first. +.It Ev REPLY +Default parameter for the +.Ic read +command if no names are given. +Also used in +.Ic select +loops to store the value that is read from standard input. +.It Ev SECONDS +The number of seconds since the shell started or, if the parameter has been +assigned an integer value, the number of seconds since the assignment plus the +value that was assigned. +.It Ev TMOUT +If set to a positive integer in an interactive shell, it specifies the maximum +number of seconds the shell will wait for input after printing the primary +prompt +.Pq Ev PS1 . +If the time is exceeded, the shell exits. +.It Ev TMPDIR +The directory temporary shell files are created in. +If this parameter is not +set, or does not contain the absolute path of a writable directory, temporary +files are created in +.Pa /tmp . +.It Ev USER_ID +The effective user id of the shell. +.El +.Ss Tilde expansion +Tilde expansion which is done in parallel with parameter substitution, is done +on words starting with an unquoted +.Ql \*(TI . +The characters following the tilde, up to the first +.Ql / , +if any, are assumed to be a login name. +If the login name is empty, +.Ql + , +or +.Ql \- , +the value of the +.Ev HOME , +.Ev PWD , +or +.Ev OLDPWD +parameter is substituted, respectively. +Otherwise, the password file is +searched for the login name, and the tilde expression is substituted with the +user's home directory. +If the login name is not found in the password file or +if any quoting or parameter substitution occurs in the login name, no +substitution is performed. +.Pp +In parameter assignments +(such as those preceding a simple-command or those occurring +in the arguments of +.Ic alias , +.Ic export , +.Ic global , +.Ic readonly , +and +.Ic typeset ) , +tilde expansion is done after any assignment +(i.e. after the equals sign) +or after an unquoted colon +.Pq Sq \&: ; +login names are also delimited by colons. +.Pp +The home directory of previously expanded login names are cached and re-used. +The +.Ic alias \-d +command may be used to list, change, and add to this cache (e.g.\& +.Ic alias \-d fac=/usr/local/facilities; cd \*(TIfac/bin ) . +.Ss Brace expansion (alteration) +Brace expressions take the following form: +.Bd -unfilled -offset indent +.Sm off +.Xo +.Ar prefix No { Ar str1 No ,..., +.Ar strN No } Ar suffix +.Xc +.Sm on +.Ed +.Pp +The expressions are expanded to +.Ar N +words, each of which is the concatenation of +.Ar prefix , +.Ar str Ns i , +and +.Ar suffix +(e.g.\& +.Dq a{c,b{X,Y},d}e +expands to four words: +.Dq ace , +.Dq abXe , +.Dq abYe , +and +.Dq ade ) . +As noted in the example, brace expressions can be nested and the resulting +words are not sorted. +Brace expressions must contain an unquoted comma +.Pq Sq \&, +for expansion to occur (e.g.\& +.Ic {} +and +.Ic {foo} +are not expanded). +Brace expansion is carried out after parameter substitution +and before file name generation. +.Ss File name patterns +A file name pattern is a word containing one or more unquoted +.Ql \&? , +.Ql * , +.Ql + , +.Ql @ , +or +.Ql \&! +characters or +.Dq \&[..] +sequences. +Once brace expansion has been performed, the shell replaces file +name patterns with the sorted names of all the files that match the pattern +(if no files match, the word is left unchanged). +The pattern elements have the following meaning: +.Bl -tag -width Ds +.It \&? +Matches any single character. +.It \&* +Matches any sequence of octets. +.It \&[..] +Matches any of the octets inside the brackets. +Ranges of octets can be specified by separating two octets by a +.Ql \- +(e.g.\& +.Dq \&[a0\-9] +matches the letter +.Sq a +or any digit). +In order to represent itself, a +.Ql \- +must either be quoted or the first or last octet in the octet list. +Similarly, a +.Ql \&] +must be quoted or the first octet in the list if it is to represent itself +instead of the end of the list. +Also, a +.Ql \&! +appearing at the start of the list has special meaning (see below), so to +represent itself it must be quoted or appear later in the list. +.It \&[!..] +Like [..], +except it matches any octet not inside the brackets. +.Sm off +.It *( Ar pattern\*(Ba No ...\*(Ba Ar pattern ) +.Sm on +Matches any string of octets that matches zero or more occurrences of the +specified patterns. +Example: The pattern +.Ic *(foo\*(Babar) +matches the strings +.Dq , +.Dq foo , +.Dq bar , +.Dq foobarfoo , +etc. +.Sm off +.It +( Ar pattern\*(Ba No ...\*(Ba Ar pattern ) +.Sm on +Matches any string of octets that matches one or more occurrences of the +specified patterns. +Example: The pattern +.Ic +(foo\*(Babar) +matches the strings +.Dq foo , +.Dq bar , +.Dq foobar , +etc. +.Sm off +.It ?( Ar pattern\*(Ba No ...\*(Ba Ar pattern ) +.Sm on +Matches the empty string or a string that matches one of the specified +patterns. +Example: The pattern +.Ic ?(foo\*(Babar) +only matches the strings +.Dq , +.Dq foo , +and +.Dq bar . +.Sm off +.It @( Ar pattern\*(Ba No ...\*(Ba Ar pattern ) +.Sm on +Matches a string that matches one of the specified patterns. +Example: The pattern +.Ic @(foo\*(Babar) +only matches the strings +.Dq foo +and +.Dq bar . +.Sm off +.It !( Ar pattern\*(Ba No ...\*(Ba Ar pattern ) +.Sm on +Matches any string that does not match one of the specified patterns. +Examples: The pattern +.Ic !(foo\*(Babar) +matches all strings except +.Dq foo +and +.Dq bar ; +the pattern +.Ic !(*) +matches no strings; the pattern +.Ic !(?)*\& +matches all strings (think about it). +.El +.Pp +Note that +.Nm mksh +.Po and Nm pdksh Pc +never matches +.Sq \&. +and +.Sq .. , +but +.At +.Nm ksh , +Bourne +.Nm sh , +and GNU +.Nm bash +do. +.Pp +Note that none of the above pattern elements match either a period +.Pq Sq \&. +at the start of a file name or a slash +.Pq Sq / , +even if they are explicitly used in a [..] sequence; also, the names +.Sq \&. +and +.Sq .. +are never matched, even by the pattern +.Sq .* . +.Pp +If the +.Ic markdirs +option is set, any directories that result from file name generation are marked +with a trailing +.Ql / . +.Ss Input/output redirection +When a command is executed, its standard input, standard output, and standard +error (file descriptors 0, 1, and 2, respectively) are normally inherited from +the shell. +Three exceptions to this are commands in pipelines, for which +standard input and/or standard output are those set up by the pipeline, +asynchronous commands created when job control is disabled, for which standard +input is initially set to be from +.Pa /dev/null , +and commands for which any of the following redirections have been specified: +.Bl -tag -width XXxxmarker +.It \*(Gt Ar file +Standard output is redirected to +.Ar file . +If +.Ar file +does not exist, it is created; if it does exist, is a regular file, and the +.Ic noclobber +option is set, an error occurs; otherwise, the file is truncated. +Note that this means the command +.Ic cmd \*(Ltfoo \*(Gtfoo +will open +.Ar foo +for reading and then truncate it when it opens it for writing, before +.Ar cmd +gets a chance to actually read +.Ar foo . +.It \*(Gt\*(Ba Ar file +Same as +.Ic \*(Gt , +except the file is truncated, even if the +.Ic noclobber +option is set. +.It \*(Gt\*(Gt Ar file +Same as +.Ic \*(Gt , +except if +.Ar file +exists it is appended to instead of being truncated. +Also, the file is opened +in append mode, so writes always go to the end of the file (see +.Xr open 2 ) . +.It \*(Lt Ar file +Standard input is redirected from +.Ar file , +which is opened for reading. +.It \*(Lt\*(Gt Ar file +Same as +.Ic \*(Lt , +except the file is opened for reading and writing. +.It \*(Lt\*(Lt Ar marker +After reading the command line containing this kind of redirection (called a +.Dq here document ) , +the shell copies lines from the command source into a temporary file until a +line matching +.Ar marker +is read. +When the command is executed, standard input is redirected from the +temporary file. +If +.Ar marker +contains no quoted characters, the contents of the temporary file are processed +as if enclosed in double quotes each time the command is executed, so +parameter, command, and arithmetic substitutions are performed, along with +backslash +.Pq Sq \e +escapes for +.Ql $ , +.Ql \` , +.Ql \e , +and +.Ql \enewline , +but not for +.Ql \&" . +If multiple here documents are used on the same command line, they are saved in +order. +.Pp +If no +.Ar marker +is given, the here document ends at the next +.Ic \*(Lt\*(Lt +and substitution will be performed. +If +.Ar marker +is only a set of either single +.Dq \*(aq\*(aq +or double +.Sq \&"" +quotes with nothing in between, the here document ends at the next empty line +and substitution will not be performed. +.It \*(Lt\*(Lt\- Ar marker +Same as +.Ic \*(Lt\*(Lt , +except leading tabs are stripped from lines in the here document. +.It \*(Lt\*(Lt\*(Lt Ar word +Same as +.Ic \*(Lt\*(Lt , +except that +.Ar word +.Em is +the here document. +This is called a here string. +.It \*(Lt& Ar fd +Standard input is duplicated from file descriptor +.Ar fd . +.Ar fd +can be a number, indicating the number of an existing file descriptor; +the letter +.Ql p , +indicating the file descriptor associated with the output of the current +co-process; or the character +.Ql \- , +indicating standard input is to be closed. +Note that +.Ar fd +is limited to a single digit in most shell implementations. +.It \*(Gt& Ar fd +Same as +.Ic \*(Lt& , +except the operation is done on standard output. +.It &\*(Gt Ar file +Same as +.Ic \*(Gt Ar file 2\*(Gt&1 . +This is a GNU +.Nm bash +extension supported by +.Nm +which also supports the preceding explicit fd number, for example, +.Ic 3&\*(Gt Ar file +is the same as +.Ic 3\*(Gt Ar file 2\*(Gt&3 +in +.Nm +but a syntax error in GNU +.Nm bash . +.It Xo +.No &\*(Gt\*(Ba Ar file , +.No &\*(Gt\*(Gt Ar file , +.No &\*(Gt& Ar fd +.Xc +Same as +.Ic \*(Gt\*(Ba Ar file , +.Ic \*(Gt\*(Gt Ar file , +or +.Ic \*(Gt& Ar fd , +followed by +.Ic 2\*(Gt&1 , +as above. +These are +.Nm +extensions. +.El +.Pp +In any of the above redirections, the file descriptor that is redirected +(i.e. standard input or standard output) +can be explicitly given by preceding the +redirection with a number (portably, only a single digit). +Parameter, command, and arithmetic +substitutions, tilde substitutions, and (if the shell is interactive) +file name generation are all performed on the +.Ar file , +.Ar marker , +and +.Ar fd +arguments of redirections. +Note, however, that the results of any file name +generation are only used if a single file is matched; if multiple files match, +the word with the expanded file name generation characters is used. +Note +that in restricted shells, redirections which can create files cannot be used. +.Pp +For simple-commands, redirections may appear anywhere in the command; for +compound-commands +.Po +.Ic if +statements, etc. +.Pc , +any redirections must appear at the end. +Redirections are processed after +pipelines are created and in the order they are given, so the following +will print an error with a line number prepended to it: +.Pp +.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t +.Pp +File descriptors created by input/output redirections are private to the +Korn shell, but passed to sub-processes if +.Fl o Ic posix +or +.Fl o Ic sh +is set. +.Ss Arithmetic expressions +Integer arithmetic expressions can be used with the +.Ic let +command, inside $((..)) expressions, inside array references (e.g.\& +.Ar name Ns Bq Ar expr ) , +as numeric arguments to the +.Ic test +command, and as the value of an assignment to an integer parameter. +.Pp +Expressions are calculated using signed arithmetic and the +.Vt mksh_ari_t +type (a 32-bit signed integer), unless they begin with a sole +.Sq # +character, in which case they use +.Vt mksh_uari_t +.Po a 32-bit unsigned integer Pc . +.Pp +Expressions may contain alpha-numeric parameter identifiers, array references, +and integer constants and may be combined with the following C operators +(listed and grouped in increasing order of precedence): +.Pp +Unary operators: +.Bd -literal -offset indent ++ \- ! \*(TI ++ \-\- +.Ed +.Pp +Binary operators: +.Bd -literal -offset indent +, += *= /= %= += \-= \*(Lt\*(Lt= \*(Gt\*(Gt= &= \*(ha= \*(Ba= +\*(Ba\*(Ba +&& +\*(Ba +\*(ha +& +== != +\*(Lt \*(Lt= \*(Gt= \*(Gt +\*(Lt\*(Lt \*(Gt\*(Gt ++ \- +* / % +.Ed +.Pp +Ternary operators: +.Bd -literal -offset indent +?: (precedence is immediately higher than assignment) +.Ed +.Pp +Grouping operators: +.Bd -literal -offset indent +( ) +.Ed +.Pp +Integer constants and expressions are calculated using the +.Vt mksh_ari_t +.Po if signed Pc +or +.Vt mksh_uari_t +.Po if unsigned Pc +type, and are limited to 32 bits. +Overflows wrap silently. +Integer constants may be specified with arbitrary bases using the notation +.Ar base Ns # Ns Ar number , +where +.Ar base +is a decimal integer specifying the base, and +.Ar number +is a number in the specified base. +Additionally, +integers may be prefixed with +.Sq 0X +or +.Sq 0x +(specifying base 16), similar to +.At +.Nm ksh , +or +.Sq 0 +(base 8), as an +.Nm +extension, in all forms of arithmetic expressions, +except as numeric arguments to the +.Ic test +command. +As a special +.Nm mksh +extension, numbers to the base of one are treated as either (8-bit +transparent) ASCII or Unicode codepoints, depending on the shell's +.Ic utf8\-mode +flag (current setting). +The +.At +.Nm ksh93 +syntax of +.Dq \*(aqx\*(aq +instead of +.Dq 1#x +is also supported. +Note that NUL bytes (integral value of zero) cannot be used. +In Unicode mode, raw octets are mapped into the range EF80..EFFF as in +OPTU-8, which is in the PUA and has been assigned by CSUR for this use. +If more than one octet in ASCII mode, or a sequence of more than one +octet not forming a valid and minimal CESU-8 sequence is passed, the +behaviour is undefined (usually, the shell aborts with a parse error, +but rarely, it succeeds, e.g. on the sequence C2 20). +That's why you should always use ASCII mode unless you know that the +input is well-formed UTF-8 in the range of 0000..FFFD. +.Pp +The operators are evaluated as follows: +.Bl -tag -width Ds -offset indent +.It unary + +Result is the argument (included for completeness). +.It unary \- +Negation. +.It \&! +Logical NOT; +the result is 1 if argument is zero, 0 if not. +.It \*(TI +Arithmetic (bit-wise) NOT. +.It ++ +Increment; must be applied to a parameter (not a literal or other expression). +The parameter is incremented by 1. +When used as a prefix operator, the result +is the incremented value of the parameter; when used as a postfix operator, the +result is the original value of the parameter. +.It \-\- +Similar to +.Ic ++ , +except the parameter is decremented by 1. +.It \&, +Separates two arithmetic expressions; the left-hand side is evaluated first, +then the right. +The result is the value of the expression on the right-hand side. +.It = +Assignment; the variable on the left is set to the value on the right. +.It Xo +.No *= /= += \-= \*(Lt\*(Lt= +.No \*(Gt\*(Gt= &= \*(ha= \*(Ba= +.Xc +Assignment operators. +.Sm off +.Ao Ar var Ac Xo +.Aq Ar op +.No = Aq Ar expr +.Xc +.Sm on +is the same as +.Sm off +.Ao Ar var Ac Xo +.No = Aq Ar var +.Aq Ar op +.Aq Ar expr , +.Xc +.Sm on +with any operator precedence in +.Aq Ar expr +preserved. +For example, +.Dq var1 *= 5 + 3 +is the same as specifying +.Dq var1 = var1 * (5 + 3) . +.It \*(Ba\*(Ba +Logical OR; +the result is 1 if either argument is non-zero, 0 if not. +The right argument is evaluated only if the left argument is zero. +.It && +Logical AND; +the result is 1 if both arguments are non-zero, 0 if not. +The right argument is evaluated only if the left argument is non-zero. +.It \*(Ba +Arithmetic (bit-wise) OR. +.It \*(ha +Arithmetic (bit-wise) XOR +(exclusive-OR). +.It & +Arithmetic (bit-wise) AND. +.It == +Equal; the result is 1 if both arguments are equal, 0 if not. +.It != +Not equal; the result is 0 if both arguments are equal, 1 if not. +.It \*(Lt +Less than; the result is 1 if the left argument is less than the right, 0 if +not. +.It \*(Lt= \*(Gt= \*(Gt +Less than or equal, greater than or equal, greater than. +See +.Ic \*(Lt . +.It \*(Lt\*(Lt \*(Gt\*(Gt +Shift left (right); the result is the left argument with its bits shifted left +(right) by the amount given in the right argument. +.It + \- * / +Addition, subtraction, multiplication, and division. +.It % +Remainder; the result is the remainder of the division of the left argument by +the right. +The sign of the result is unspecified if either argument is negative. +.It Xo +.Sm off +.Aq Ar arg1 ? +.Aq Ar arg2 : +.Aq Ar arg3 +.Sm on +.Xc +If +.Aq Ar arg1 +is non-zero, the result is +.Aq Ar arg2 ; +otherwise the result is +.Aq Ar arg3 . +.El +.Ss Co-processes +A co-process (which is a pipeline created with the +.Sq \*(Ba& +operator) is an asynchronous process that the shell can both write to (using +.Ic print \-p ) +and read from (using +.Ic read \-p ) . +The input and output of the co-process can also be manipulated using +.Ic \*(Gt&p +and +.Ic \*(Lt&p +redirections, respectively. +Once a co-process has been started, another can't +be started until the co-process exits, or until the co-process's input has been +redirected using an +.Ic exec Ar n Ns Ic \*(Gt&p +redirection. +If a co-process's input is redirected in this way, the next +co-process to be started will share the output with the first co-process, +unless the output of the initial co-process has been redirected using an +.Ic exec Ar n Ns Ic \*(Lt&p +redirection. +.Pp +Some notes concerning co-processes: +.Bl -bullet +.It +The only way to close the co-process's input (so the co-process reads an +end-of-file) is to redirect the input to a numbered file descriptor and then +close that file descriptor: +.Ic exec 3\*(Gt&p; exec 3\*(Gt&\- +.It +In order for co-processes to share a common output, the shell must keep the +write portion of the output pipe open. +This means that end-of-file will not be +detected until all co-processes sharing the co-process's output have exited +(when they all exit, the shell closes its copy of the pipe). +This can be +avoided by redirecting the output to a numbered file descriptor (as this also +causes the shell to close its copy). +Note that this behaviour is slightly +different from the original Korn shell which closes its copy of the write +portion of the co-process output when the most recently started co-process +(instead of when all sharing co-processes) exits. +.It +.Ic print \-p +will ignore +.Dv SIGPIPE +signals during writes if the signal is not being trapped or ignored; the same +is true if the co-process input has been duplicated to another file descriptor +and +.Ic print \-u Ns Ar n +is used. +.El +.Ss Functions +Functions are defined using either Korn shell +.Ic function Ar function-name +syntax or the Bourne/POSIX shell +.Ar function-name Ns \&() +syntax (see below for the difference between the two forms). +Functions are like +.Li .\(hyscripts +(i.e. scripts sourced using the +.Sq \&. +built-in) +in that they are executed in the current environment. +However, unlike +.Li .\(hyscripts , +shell arguments (i.e. positional parameters $1, $2, etc.)\& +are never visible inside them. +When the shell is determining the location of a command, functions +are searched after special built-in commands, before regular and +non-regular built-ins, and before the +.Ev PATH +is searched. +.Pp +An existing function may be deleted using +.Ic unset Fl f Ar function-name . +A list of functions can be obtained using +.Ic typeset +f +and the function definitions can be listed using +.Ic typeset \-f . +The +.Ic autoload +command (which is an alias for +.Ic typeset \-fu ) +may be used to create undefined functions: when an undefined function is +executed, the shell searches the path specified in the +.Ev FPATH +parameter for a file with the same name as the function which, if found, is +read and executed. +If after executing the file the named function is found to +be defined, the function is executed; otherwise, the normal command search is +continued (i.e. the shell searches the regular built-in command table and +.Ev PATH ) . +Note that if a command is not found using +.Ev PATH , +an attempt is made to autoload a function using +.Ev FPATH +(this is an undocumented feature of the original Korn shell). +.Pp +Functions can have two attributes, +.Dq trace +and +.Dq export , +which can be set with +.Ic typeset \-ft +and +.Ic typeset \-fx , +respectively. +When a traced function is executed, the shell's +.Ic xtrace +option is turned on for the function's duration. +The +.Dq export +attribute of functions is currently not used. +In the original Korn shell, +exported functions are visible to shell scripts that are executed. +.Pp +Since functions are executed in the current shell environment, parameter +assignments made inside functions are visible after the function completes. +If this is not the desired effect, the +.Ic typeset +command can be used inside a function to create a local parameter. +Note that +.At +.Nm ksh93 +uses static scoping (one global scope, one local scope per function), whereas +.Nm mksh +uses dynamic scoping (nested scopes of varying locality). +Note that special parameters (e.g.\& +.Ic \&$$ , $! ) +can't be scoped in this way. +.Pp +The exit status of a function is that of the last command executed in the +function. +A function can be made to finish immediately using the +.Ic return +command; this may also be used to explicitly specify the exit status. +.Pp +Functions defined with the +.Ic function +reserved word are treated differently in the following ways from functions +defined with the +.Ic \&() +notation: +.Bl -bullet +.It +The $0 parameter is set to the name of the function +(Bourne-style functions leave $0 untouched). +.It +Parameter assignments preceding function calls are not kept in the shell +environment (executing Bourne-style functions will keep assignments). +.It +.Ev OPTIND +is saved/reset and restored on entry and exit from the function so +.Ic getopts +can be used properly both inside and outside the function (Bourne-style +functions leave +.Ev OPTIND +untouched, so using +.Ic getopts +inside a function interferes with using +.Ic getopts +outside the function). +.It +Bourne-style function definitions take precedence over alias dereferences +and remove alias definitions upon encounter, while aliases take precedence +over Korn-style functions. +.El +.Pp +In the future, the following differences will also be added: +.Bl -bullet +.It +A separate trap/signal environment will be used during the execution of +functions. +This will mean that traps set inside a function will not affect the +shell's traps and signals that are not ignored in the shell (but may be +trapped) will have their default effect in a function. +.It +The EXIT trap, if set in a function, will be executed after the function +returns. +.El +.Ss Command execution +After evaluation of command-line arguments, redirections, and parameter +assignments, the type of command is determined: a special built-in, a +function, a regular built-in, or the name of a file to execute found using the +.Ev PATH +parameter. +The checks are made in the above order. +Special built-in commands differ from other commands in that the +.Ev PATH +parameter is not used to find them, an error during their execution can +cause a non-interactive shell to exit, and parameter assignments that are +specified before the command are kept after the command completes. +Regular built-in commands are different only in that the +.Ev PATH +parameter is not used to find them. +.Pp +The original +.Nm ksh +and POSIX differ somewhat in which commands are considered +special or regular: +.Pp +POSIX special commands +.Pp +.Ic \&. , \&: , break , continue , +.Ic eval , exec , exit , export , +.Ic readonly , return , set , shift , +.Ic trap , unset , wait +.Pp +Additional +.Nm +special commands +.Pp +.Ic builtin , global , times , typeset +.Pp +Very special commands +.Pq non-POSIX +.Pp +.Ic alias , readonly , set , typeset +.Pp +POSIX regular commands +.Pp +.Ic alias , bg , cd , command , +.Ic false , fc , fg , getopts , +.Ic jobs , kill , read , true , +.Ic umask , unalias +.Pp +Additional +.Nm +regular commands +.Pp +.Ic \&[ , chdir , bind , cat , +.Ic echo , let , mknod , print , +.Ic printf , pwd , realpath , rename , +.Ic sleep , test , ulimit , whence +.Pp +In the future, the additional +.Nm +special and regular commands may be treated +differently from the POSIX special and regular commands. +.Pp +Once the type of command has been determined, any command-line parameter +assignments are performed and exported for the duration of the command. +.Pp +The following describes the special and regular built-in commands: +.Pp +.Bl -tag -width false -compact +.It Ic \&. Ar file Op Ar arg ... +This is called the +.Dq dot +command. +Execute the commands in +.Ar file +in the current environment. +The file is searched for in the directories of +.Ev PATH . +If arguments are given, the positional parameters may be used to access them +while +.Ar file +is being executed. +If no arguments are given, the positional parameters are +those of the environment the command is used in. +.Pp +.It Ic \&: Op Ar ... +The null command. +Exit status is set to zero. +.Pp +.It Xo Ic alias +.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba +.Cm +\-x Oc +.Op Fl p +.Op Cm + +.Oo Ar name +.Op Ns = Ns Ar value +.Ar ... Oc +.Xc +Without arguments, +.Ic alias +lists all aliases. +For any name without a value, the existing alias is listed. +Any name with a value defines an alias (see +.Sx Aliases +above). +.Pp +When listing aliases, one of two formats is used. +Normally, aliases are listed as +.Ar name Ns = Ns Ar value , +where +.Ar value +is quoted. +If options were preceded with +.Ql + , +or a lone +.Ql + +is given on the command line, only +.Ar name +is printed. +.Pp +The +.Fl d +option causes directory aliases which are used in tilde expansion to be +listed or set (see +.Sx Tilde expansion +above). +.Pp +If the +.Fl p +option is used, each alias is prefixed with the string +.Dq alias\ \& . +.Pp +The +.Fl t +option indicates that tracked aliases are to be listed/set (values specified on +the command line are ignored for tracked aliases). +The +.Fl r +option indicates that all tracked aliases are to be reset. +.Pp +The +.Fl x +option sets +.Pq Ic +x No clears +the export attribute of an alias, or, if no names are given, lists the aliases +with the export attribute (exporting an alias has no effect). +.Pp +.It Ic bg Op Ar job ... +Resume the specified stopped job(s) in the background. +If no jobs are specified, +.Ic %+ +is assumed. +See +.Sx Job control +below for more information. +.Pp +.It Ic bind Op Fl l +The current bindings are listed. +If the +.Fl l +flag is given, +.Ic bind +instead lists the names of the functions to which keys may be bound. +See +.Sx Emacs editing mode +for more information. +.Pp +.It Xo Ic bind Op Fl m +.Ar string Ns = Ns Op Ar substitute +.Ar ... +.Xc +.It Xo Ic bind +.Ar string Ns = Ns Op Ar editing-command +.Ar ... +.Xc +The specified editing command is bound to the given +.Ar string , +which should consist of a control character +optionally preceded by one of the two prefix characters +and optionally succeded by a tilde character. +Future input of the +.Ar string +will cause the editing command to be immediately invoked. +If the +.Fl m +flag is given, the specified input +.Ar string +will afterwards be immediately replaced by the given +.Ar substitute +string which may contain editing commands but not other macros. +If a tilde postfix is given, a tilde trailing the one or +two prefices and the control character is ignored, any +other trailing character will be processed afterwards. +.Pp +Control characters may be written using caret notation +i.e. \*(haX represents Ctrl-X. +Note that although only two prefix characters (usually ESC and \*(haX) +are supported, some multi-character sequences can be supported. +.Pp +The following default bindings show how the arrow keys, the home, end and +delete key on a BSD wsvt25, xterm\-xfree86 or GNU screen terminal are bound +(of course some escape sequences won't work out quite this nicely): +.Bd -literal -offset indent +bind \*(aq\*(haX\*(aq=prefix\-2 +bind \*(aq\*(ha[[\*(aq=prefix\-2 +bind \*(aq\*(haXA\*(aq=up\-history +bind \*(aq\*(haXB\*(aq=down\-history +bind \*(aq\*(haXC\*(aq=forward\-char +bind \*(aq\*(haXD\*(aq=backward\-char +bind \*(aq\*(haX1\*(TI\*(aq=beginning\-of\-line +bind \*(aq\*(haX7\*(TI\*(aq=beginning\-of\-line +bind \*(aq\*(haXH\*(aq=beginning\-of\-line +bind \*(aq\*(haX4\*(TI\*(aq=end\-of\-line +bind \*(aq\*(haX8\*(TI\*(aq=end\-of\-line +bind \*(aq\*(haXF\*(aq=end\-of\-line +bind \*(aq\*(haX3\*(TI\*(aq=delete\-char\-forward +.Ed +.Pp +.It Ic break Op Ar level +Exit the +.Ar level Ns th +inner-most +.Ic for , +.Ic select , +.Ic until , +or +.Ic while +loop. +.Ar level +defaults to 1. +.Pp +.It Xo +.Ic builtin +.Op Fl \- +.Ar command Op Ar arg ... +.Xc +Execute the built-in command +.Ar command . +.Pp +.It Xo +.Ic cat +.Op Fl u +.Op Ar +.Xc +Read files sequentially, in command line order, and write them to +standard output. +If a +.Ar file +is a single dash +.Pq Sq - +or absent, read from standard input. +Unless compiled with +.Dv MKSH_NO_EXTERNAL_CAT , +if any options are given, an external +.Xr cat 1 +utility is invoked instead if called from the shell. +For direct builtin calls, the +.Tn POSIX +.Fl u +option is supported as a no-op. +.Pp +.It Xo +.Ic cd +.Op Fl L +.Op Ar dir +.Xc +.It Xo +.Ic cd +.Fl P Op Fl e +.Op Ar dir +.Xc +.It Xo +.Ic chdir +.Op Fl eLP +.Op Ar dir +.Xc +Set the working directory to +.Ar dir . +If the parameter +.Ev CDPATH +is set, it lists the search path for the directory containing +.Ar dir . +A +.Dv NULL +path means the current directory. +If +.Ar dir +is found in any component of the +.Ev CDPATH +search path other than the +.Dv NULL +path, the name of the new working directory will be written to standard output. +If +.Ar dir +is missing, the home directory +.Ev HOME +is used. +If +.Ar dir +is +.Ql \- , +the previous working directory is used (see the +.Ev OLDPWD +parameter). +.Pp +If the +.Fl L +option (logical path) is used or if the +.Ic physical +option isn't set (see the +.Ic set +command below), references to +.Sq .. +in +.Ar dir +are relative to the path used to get to the directory. +If the +.Fl P +option (physical path) is used or if the +.Ic physical +option is set, +.Sq .. +is relative to the filesystem directory tree. +The +.Ev PWD +and +.Ev OLDPWD +parameters are updated to reflect the current and old working directory, +respectively. +If the +.Fl e +option is set for physical filesystem traversal, and +.Ev PWD +could not be set, the exit code is 1; greater than 1 if an +error occurred, 0 otherwise. +.Pp +.It Xo +.Ic cd +.Op Fl eLP +.Ar old new +.Xc +.It Xo +.Ic chdir +.Op Fl eLP +.Ar old new +.Xc +The string +.Ar new +is substituted for +.Ar old +in the current directory, and the shell attempts to change to the new +directory. +.Pp +.It Xo +.Ic command +.Op Fl pVv +.Ar cmd +.Op Ar arg ... +.Xc +If neither the +.Fl v +nor +.Fl V +option is given, +.Ar cmd +is executed exactly as if +.Ic command +had not been specified, with two exceptions: +firstly, +.Ar cmd +cannot be a shell function; +and secondly, special built-in commands lose their specialness +(i.e. redirection and utility errors do not cause the shell to +exit, and command assignments are not permanent). +.Pp +If the +.Fl p +option is given, a default search path is used instead of the current value of +.Ev PATH , +the actual value of which is system dependent. +.Pp +If the +.Fl v +option is given, instead of executing +.Ar cmd , +information about what would be executed is given (and the same is done for +.Ar arg ... ) . +For special and regular built-in commands and functions, their names are simply +printed; for aliases, a command that defines them is printed; and for commands +found by searching the +.Ev PATH +parameter, the full path of the command is printed. +If no command is found +(i.e. the path search fails), nothing is printed and +.Ic command +exits with a non-zero status. +The +.Fl V +option is like the +.Fl v +option, except it is more verbose. +.Pp +.It Ic continue Op Ar level +Jumps to the beginning of the +.Ar level Ns th +inner-most +.Ic for , +.Ic select , +.Ic until , +or +.Ic while +loop. +.Ar level +defaults to 1. +.Pp +.It Xo +.Ic echo +.Op Fl Een +.Op Ar arg ... +.Xc +.Em Warning: +this utility is not portable; use the Korn shell builtin +.Ic print +or the much slower POSIX utility +.Ic printf +instead. +.Pp +Prints its arguments (separated by spaces) followed by a newline, to the +standard output. +The newline is suppressed if any of the arguments contain the +backslash sequence +.Ql \ec . +See the +.Ic print +command below for a list of other backslash sequences that are recognised. +.Pp +The options are provided for compatibility with +.Bx +shell scripts. +The +.Fl n +option suppresses the trailing newline, +.Fl e +enables backslash interpretation (a no-op, since this is normally done), and +.Fl E +suppresses backslash interpretation. +.Pp +If the +.Ic posix +or +.Ic sh +option is set or this is a direct builtin call, only the first argument +is treated as an option, and only if it is exactly +.Dq Fl n . +Backslash interpretation is disabled. +.Pp +.It Ic eval Ar command ... +The arguments are concatenated (with spaces between them) to form a single +string which the shell then parses and executes in the current environment. +.Pp +.It Xo +.Ic exec +.Op Ar command Op Ar arg ... +.Xc +The command is executed without forking, replacing the shell process. +.Pp +If no command is given except for I/O redirection, the I/O redirection is +permanent and the shell is +not replaced. +Any file descriptors greater than 2 which are opened or +.Xr dup 2 Ns 'd +in this way are not made available to other executed commands (i.e. commands +that are not built-in to the shell). +Note that the Bourne shell differs here; +it does pass these file descriptors on. +.Pp +.It Ic exit Op Ar status +The shell exits with the specified exit status. +If +.Ar status +is not specified, the exit status is the current value of the +.Ic $?\& +parameter. +.Pp +.It Xo +.Ic export +.Op Fl p +.Op Ar parameter Ns Op = Ns Ar value +.Xc +Sets the export attribute of the named parameters. +Exported parameters are passed in the environment to executed commands. +If values are specified, the named parameters are also assigned. +.Pp +If no parameters are specified, the names of all parameters with the export +attribute are printed one per line, unless the +.Fl p +option is used, in which case +.Ic export +commands defining all exported parameters, including their values, are printed. +.Pp +.It Ic false +A command that exits with a non-zero status. +.Pp +.It Xo +.Ic fc +.Oo Fl e Ar editor \*(Ba +.Fl l Op Fl n Oc +.Op Fl r +.Op Ar first Op Ar last +.Xc +.Ar first +and +.Ar last +select commands from the history. +Commands can be selected by history number +or a string specifying the most recent command starting with that string. +The +.Fl l +option lists the command on standard output, and +.Fl n +inhibits the default command numbers. +The +.Fl r +option reverses the order of the list. +Without +.Fl l , +the selected commands are edited by the editor specified with the +.Fl e +option, or if no +.Fl e +is specified, the editor specified by the +.Ev FCEDIT +parameter (if this parameter is not set, +.Pa /bin/ed +is used), and then executed by the shell. +.Pp +.It Xo +.Ic fc +.Cm \-e \- \*(Ba Fl s +.Op Fl g +.Op Ar old Ns = Ns Ar new +.Op Ar prefix +.Xc +Re-execute the selected command (the previous command by default) after +performing the optional substitution of +.Ar old +with +.Ar new . +If +.Fl g +is specified, all occurrences of +.Ar old +are replaced with +.Ar new . +The meaning of +.Cm \-e \- +and +.Fl s +is identical: re-execute the selected command without invoking an editor. +This command is usually accessed with the predefined +.Ic alias r=\*(aqfc \-e \-\*(aq +or by prefixing an interactive mode input line with +.Sq \&! +.Pq wbx extension . +.Pp +.It Ic fg Op Ar job ... +Resume the specified job(s) in the foreground. +If no jobs are specified, +.Ic %+ +is assumed. +See +.Sx Job control +below for more information. +.Pp +.It Xo +.Ic getopts +.Ar optstring name +.Op Ar arg ... +.Xc +Used by shell procedures to parse the specified arguments (or positional +parameters, if no arguments are given) and to check for legal options. +.Ar optstring +contains the option letters that +.Ic getopts +is to recognise. +If a letter is followed by a colon, the option is expected to +have an argument. +Options that do not take arguments may be grouped in a single argument. +If an option takes an argument and the option character is not the +last character of the argument it is found in, the remainder of the argument is +taken to be the option's argument; otherwise, the next argument is the option's +argument. +.Pp +Each time +.Ic getopts +is invoked, it places the next option in the shell parameter +.Ar name +and the index of the argument to be processed by the next call to +.Ic getopts +in the shell parameter +.Ev OPTIND . +If the option was introduced with a +.Ql + , +the option placed in +.Ar name +is prefixed with a +.Ql + . +When an option requires an argument, +.Ic getopts +places it in the shell parameter +.Ev OPTARG . +.Pp +When an illegal option or a missing option argument is encountered, a question +mark or a colon is placed in +.Ar name +(indicating an illegal option or missing argument, respectively) and +.Ev OPTARG +is set to the option character that caused the problem. +Furthermore, if +.Ar optstring +does not begin with a colon, a question mark is placed in +.Ar name , +.Ev OPTARG +is unset, and an error message is printed to standard error. +.Pp +When the end of the options is encountered, +.Ic getopts +exits with a non-zero exit status. +Options end at the first (non-option +argument) argument that does not start with a +.Ql \- , +or when a +.Ql \-\- +argument is encountered. +.Pp +Option parsing can be reset by setting +.Ev OPTIND +to 1 (this is done automatically whenever the shell or a shell procedure is +invoked). +.Pp +Warning: Changing the value of the shell parameter +.Ev OPTIND +to a value other than 1, or parsing different sets of arguments without +resetting +.Ev OPTIND , +may lead to unexpected results. +.Pp +.It Xo +.Ic hash +.Op Fl r +.Op Ar name ... +.Xc +Without arguments, any hashed executable command pathnames are listed. +The +.Fl r +option causes all hashed commands to be removed from the hash table. +Each +.Ar name +is searched as if it were a command name and added to the hash table if it is +an executable command. +.Pp +.It Xo +.Ic jobs +.Op Fl lnp +.Op Ar job ... +.Xc +Display information about the specified job(s); if no jobs are specified, all +jobs are displayed. +The +.Fl n +option causes information to be displayed only for jobs that have changed +state since the last notification. +If the +.Fl l +option is used, the process ID of each process in a job is also listed. +The +.Fl p +option causes only the process group of each job to be printed. +See +.Sx Job control +below for the format of +.Ar job +and the displayed job. +.Pp +.It Xo +.Ic kill +.Oo Fl s Ar signame \*(Ba +.No \- Ns Ar signum \*(Ba +.No \- Ns Ar signame Oc +.No { Ar job \*(Ba pid \*(Ba pgrp No } +.Ar ... +.Xc +Send the specified signal to the specified jobs, process IDs, or process +groups. +If no signal is specified, the +.Dv TERM +signal is sent. +If a job is specified, the signal is sent to the job's process group. +See +.Sx Job control +below for the format of +.Ar job . +.Pp +.It Xo +.Ic kill +.Fl l +.Op Ar exit-status ... +.Xc +Print the signal name corresponding to +.Ar exit-status . +If no arguments are specified, a list of all the signals, their numbers, and +a short description of them are printed. +.Pp +.It Ic let Op Ar expression ... +Each expression is evaluated (see +.Sx Arithmetic expressions +above). +If all expressions are successfully evaluated, the exit status is 0 (1) +if the last expression evaluated to non-zero (zero). +If an error occurs during +the parsing or evaluation of an expression, the exit status is greater than 1. +Since expressions may need to be quoted, +.No \&(( Ar expr No )) +is syntactic sugar for +.No let \&" Ns Ar expr Ns \&" . +.Pp +.It Xo +.Ic mknod +.Op Fl m Ar mode +.Ar name +.Cm b\*(Bac +.Ar major minor +.Xc +.It Xo +.Ic mknod +.Op Fl m Ar mode +.Ar name +.Cm p +.Xc +Create a device special file. +The file type may be +.Cm b +(block type device), +.Cm c +(character type device), +or +.Cm p +(named pipe). +The file created may be modified according to its +.Ar mode +(via the +.Fl m +option), +.Ar major +(major device number), +and +.Ar minor +(minor device number). +.Pp +See +.Xr mknod 8 +for further information. +.Pp +.It Xo +.Ic print +.Oo Fl nprsu Ns Oo Ar n Oc \*(Ba +.Fl R Op Fl en Oc +.Op Ar argument ... +.Xc +.Ic print +prints its arguments on the standard output, separated by spaces and +terminated with a newline. +The +.Fl n +option suppresses the newline. +By default, certain C escapes are translated. +These include these mentioned in +.Sx Backslash expansion +above, as well as +.Ql \ec , +which is equivalent to using the +.Fl n +option. +Backslash expansion may be inhibited with the +.Fl r +option. +The +.Fl s +option prints to the history file instead of standard output; the +.Fl u +option prints to file descriptor +.Ar n +.Po +.Ar n +defaults to 1 if omitted +.Pc ; +and the +.Fl p +option prints to the co-process (see +.Sx Co-processes +above). +.Pp +The +.Fl R +option is used to emulate, to some degree, the +.Bx +.Xr echo 1 +command which does not process +.Ql \e +sequences unless the +.Fl e +option is given. +As above, the +.Fl n +option suppresses the trailing newline. +.Pp +.It Ic printf Ar format Op Ar arguments ... +Formatted output. +Approximately the same as the utility +.Ic printf , +except that it uses the same +.Sx Backslash expansion +and I/O code as the rest of +.Nm mksh . +This is not normally part of +.Nm mksh ; +however, distributors may have added this as builtin as a speed hack. +.Pp +.It Ic pwd Op Fl LP +Print the present working directory. +If the +.Fl L +option is used or if the +.Ic physical +option isn't set (see the +.Ic set +command below), the logical path is printed (i.e. the path used to +.Ic cd +to the current directory). +If the +.Fl P +option (physical path) is used or if the +.Ic physical +option is set, the path determined from the filesystem (by following +.Sq .. +directories to the root directory) is printed. +.Pp +.It Xo +.Ic read +.Op Fl A | Fl a +.Op Fl d Ar x +.Oo Fl N Ar z \*(Ba +.Fl n Ar z Oc +.Oo Fl p \*(Ba +.Fl u Ns Op Ar n +.Oc Op Fl t Ar n +.Op Fl rs +.Op Ar p ... +.Xc +Reads a line of input, separates the input into fields using the +.Ev IFS +parameter (see +.Sx Substitution +above), and assigns each field to the specified parameters +.Ar p . +If no parameters are specified, the +.Ev REPLY +parameter is used to store the result. +With the +.Fl A +and +.Fl a +options, only no or one parameter is accepted. +If there are more parameters than fields, the extra parameters are set to +the empty string or 0; if there are more fields than parameters, the last +parameter is assigned the remaining fields (including the word separators). +.Pp +The options are as follows: +.Bl -tag -width XuXnX +.It Fl A +Store the result into the parameter +.Ar p +(or +.Ev REPLY ) +as array of words. +.It Fl a +Store the result without word splitting into the parameter +.Ar p +(or +.Ev REPLY ) +as array of characters (wide characters if the +.Ic utf8\-mode +option is enacted, octets otherwise). +.It Fl d Ar x +Use the first byte of +.Ar x , +.Dv NUL +if empty, instead of the ASCII newline character as input line delimiter. +.It Fl N Ar z +Instead of reading till end-of-line, read exactly +.Ar z +bytes; less if EOF or a timeout occurs. +.It Fl n Ar z +Instead of reading till end-of-line, read up to +.Ar z +bytes but return as soon as any bytes are read, e.g.\& from a +slow terminal device, or if EOF or a timeout occurs. +.It Fl p +Read from the currently active co-process, see +.Sx Co-processes +above for details on this. +.It Fl u Ns Op Ar n +Read from the file descriptor +.Ar n +(defaults to 0, i.e.\& standard input). +The argument must immediately follow the option character. +.It Fl t Ar n +Interrupt reading after +.Ar n +seconds (specified as positive decimal value with an optional fractional part). +.It Fl r +Normally, the ASCII backslash character escapes the special +meaning of the following character and is stripped from the input; +.Ic read +does not stop when encountering a backslash-newline sequence and +does not store that newline in the result. +This option enables raw mode, in which backslashes are not processed. +.It Fl s +The input line is saved to the history. +.El +.Pp +If the input is a terminal, both the +.Fl N +and +.Fl n +options set it into raw mode; +they read an entire file if \-1 is passed as +.Ar z +argument. +.Pp +The first parameter may have a question mark and a string appended to it, in +which case the string is used as a prompt (printed to standard error before +any input is read) if the input is a +.Xr tty 4 +(e.g.\& +.Ic read nfoo?\*(aqnumber of foos: \*(aq ) . +.Pp +If no input is read or a timeout occurred, +.Ic read +exits with a non-zero status. +.Pp +Another handy set of tricks: +If +.Ic read +is run in a loop such as +.Ic while read foo; do ...; done +then leading whitespace will be removed (IFS) and backslashes processed. +You might want to use +.Ic while IFS= read \-r foo; do ...; done +for pristine I/O. +Similarily, when using the +.Fl a +option, use of the +.Fl r +option might be prudent; the same applies for: +.Bd -literal -offset indent +find . \-type f \-print0 \*(Ba \e + while IFS= read \-d \*(aq\*(aq \-r filename; do + print \-r \-\- "found <${filename#./}>" +done +.Ed +.Pp +The inner loop will be executed in a subshell and variable changes +cannot be propagated if executed in a pipeline: +.Bd -literal -offset indent +bar \*(Ba baz \*(Ba while read foo; do ...; done +.Ed +.Pp +Use co-processes instead: +.Bd -literal -offset indent +bar \*(Ba baz \*(Ba& +while read \-p foo; do ...; done +exec 3\*(Gt&p; exec 3\*(Gt&\- +.Ed +.Pp +.It Xo +.Ic readonly +.Op Fl p +.Oo Ar parameter +.Op Ns = Ns Ar value +.Ar ... Oc +.Xc +Sets the read-only attribute of the named parameters. +If values are given, +parameters are set to them before setting the attribute. +Once a parameter is +made read-only, it cannot be unset and its value cannot be changed. +.Pp +If no parameters are specified, the names of all parameters with the read-only +attribute are printed one per line, unless the +.Fl p +option is used, in which case +.Ic readonly +commands defining all read-only parameters, including their values, are +printed. +.Pp +.It Xo +.Ic realpath +.Op Fl \- +.Ar name +.Xc +Prints the resolved absolute pathname corresponding to +.Ar name . +If +.Ar name +ends with a slash +.Pq Sq / , +it's also checked for existence and whether it is a directory; otherwise, +.Ic realpath +returns 0 if the pathname either exists or can be created immediately, +i.e. all but the last component exist and are directories. +.Pp +.It Xo +.Ic rename +.Op Fl \- +.Ar from to +.Xc +Renames the file +.Ar from +to +.Ar to . +Both must be complete pathnames and on the same device. +This builtin is intended for emergency situations where +.Pa /bin/mv +becomes unusable, and directly calls +.Xr rename 2 . +.Pp +.It Ic return Op Ar status +Returns from a function or +.Ic .\& +script, with exit status +.Ar status . +If no +.Ar status +is given, the exit status of the last executed command is used. +If used outside of a function or +.Ic .\& +script, it has the same effect as +.Ic exit . +Note that +.Nm +treats both profile and +.Ev ENV +files as +.Ic .\& +scripts, while the original Korn shell only treats profiles as +.Ic .\& +scripts. +.Pp +.It Xo +.Ic set Op Ic +\-abCefhiklmnprsUuvXx +.Op Ic +\-o Ar option +.Op Ic +\-A Ar name +.Op Fl \- +.Op Ar arg ... +.Xc +The +.Ic set +command can be used to set +.Pq Ic \- +or clear +.Pq Ic + +shell options, set the positional parameters, or set an array parameter. +Options can be changed using the +.Cm +\-o Ar option +syntax, where +.Ar option +is the long name of an option, or using the +.Cm +\- Ns Ar letter +syntax, where +.Ar letter +is the option's single letter name (not all options have a single letter name). +The following table lists both option letters (if they exist) and long names +along with a description of what the option does: +.Bl -tag -width 3n +.It Fl A Ar name +Sets the elements of the array parameter +.Ar name +to +.Ar arg ... +If +.Fl A +is used, the array is reset (i.e. emptied) first; if +.Ic +A +is used, the first N elements are set (where N is the number of arguments); +the rest are left untouched. +.Pp +An alternative syntax for the command +.Ic set \-A foo \-\- a b c +which is compatible to +.Tn GNU +.Nm bash +and also supported by +.At +.Nm ksh93 +is: +.Ic foo=(a b c); foo+=(d e) +.Pp +Another +.At +.Nm ksh93 +and +.Tn GNU +.Nm bash +extension allows specifying the indices used for +.Ar arg ... +.Pq from the above example, Ic a b c +like this: +.Ic set \-A foo \-\- [0]=a [1]=b [2]=c +or +.Ic foo=([0]=a [1]=b [2]=c) +which can also be written +.Ic foo=([0]=a b c) +because indices are incremented automatically. +.It Fl a \*(Ba Fl o Ic allexport +All new parameters are created with the export attribute. +.It Fl b \*(Ba Fl o Ic notify +Print job notification messages asynchronously, instead of just before the +prompt. +Only used if job control is enabled +.Pq Fl m . +.It Fl C \*(Ba Fl o Ic noclobber +Prevent \*(Gt redirection from overwriting existing files. +Instead, \*(Gt\*(Ba must be used to force an overwrite. +.It Fl e \*(Ba Fl o Ic errexit +Exit (after executing the +.Dv ERR +trap) as soon as an error occurs or a command fails (i.e. exits with a +non-zero status). +This does not apply to commands whose exit status is +explicitly tested by a shell construct such as +.Ic if , +.Ic until , +.Ic while , +.Ic && , +.Ic \*(Ba\*(Ba , +or +.Ic !\& +statements. +.It Fl f \*(Ba Fl o Ic noglob +Do not expand file name patterns. +.It Fl h \*(Ba Fl o Ic trackall +Create tracked aliases for all executed commands (see +.Sx Aliases +above). +Enabled by default for non-interactive shells. +.It Fl i \*(Ba Fl o Ic interactive +The shell is an interactive shell. +This option can only be used when the shell is invoked. +See above for a description of what this means. +.It Fl k \*(Ba Fl o Ic keyword +Parameter assignments are recognised anywhere in a command. +.It Fl l \*(Ba Fl o Ic login +The shell is a login shell. +This option can only be used when the shell is invoked. +See above for a description of what this means. +.It Fl m \*(Ba Fl o Ic monitor +Enable job control (default for interactive shells). +.It Fl n \*(Ba Fl o Ic noexec +Do not execute any commands. +Useful for checking the syntax of scripts +(ignored if interactive). +.It Fl p \*(Ba Fl o Ic privileged +The shell is a privileged shell. +It is set automatically if, when the shell starts, +the real UID or GID does not match +the effective UID (EUID) or GID (EGID), respectively. +See above for a description of what this means. +.It Fl r \*(Ba Fl o Ic restricted +The shell is a restricted shell. +This option can only be used when the shell is invoked. +See above for a description of what this means. +.It Fl s \*(Ba Fl o Ic stdin +If used when the shell is invoked, commands are read from standard input. +Set automatically if the shell is invoked with no arguments. +.Pp +When +.Fl s +is used with the +.Ic set +command it causes the specified arguments to be sorted before assigning them to +the positional parameters (or to array +.Ar name , +if +.Fl A +is used). +.It Fl U \*(Ba Fl o Ic utf8\-mode +Enable UTF-8 support in the +.Sx Emacs editing mode +and internal string handling functions. +This flag is disabled by default, but can be enabled by setting it on the +shell command line; is enabled automatically for interactive shells if +requested at compile time, your system supports +.Fn setlocale LC_CTYPE \&"" +and optionally +.Fn nl_langinfo CODESET , +or the +.Ev LC_ALL , +.Ev LC_CTYPE , +or +.Ev LANG +environment variables, +and at least one of these returns something that matches +.Dq UTF\-8 +or +.Dq utf8 +case-insensitively; for direct builtin calls depending on the +aforementioned environment variables; or for stdin or scripts, +if the input begins with a UTF-8 Byte Order Mark. +.It Fl u \*(Ba Fl o Ic nounset +Referencing of an unset parameter, other than +.Dq $@ +or +.Dq $* , +is treated as an error, unless one of the +.Ql \- , +.Ql + , +or +.Ql = +modifiers is used. +.It Fl v \*(Ba Fl o Ic verbose +Write shell input to standard error as it is read. +.It Fl X \*(Ba Fl o Ic markdirs +Mark directories with a trailing +.Ql / +during file name generation. +.It Fl x \*(Ba Fl o Ic xtrace +Print commands and parameter assignments when they are executed, preceded by +the value of +.Ev PS4 . +.It Fl o Ic bgnice +Background jobs are run with lower priority. +.It Fl o Ic braceexpand +Enable brace expansion (a.k.a. alternation). +This is enabled by default. +If disabled, tilde expansion after an equals sign is disabled as a side effect. +.It Fl o Ic emacs +Enable BRL emacs-like command-line editing (interactive shells only); see +.Sx Emacs editing mode . +.It Fl o Ic gmacs +Enable gmacs-like command-line editing (interactive shells only). +Currently identical to emacs editing except that transpose\-chars (\*(haT) acts +slightly differently. +.It Fl o Ic ignoreeof +The shell will not (easily) exit when end-of-file is read; +.Ic exit +must be used. +To avoid infinite loops, the shell will exit if +.Dv EOF +is read 13 times in a row. +.It Fl o Ic nohup +Do not kill running jobs with a +.Dv SIGHUP +signal when a login shell exits. +Currently set by default, but this may +change in the future to be compatible with +.At +.Nm ksh , +which +doesn't have this option, but does send the +.Dv SIGHUP +signal. +.It Fl o Ic nolog +No effect. +In the original Korn shell, this prevents function definitions from +being stored in the history file. +.It Fl o Ic physical +Causes the +.Ic cd +and +.Ic pwd +commands to use +.Dq physical +(i.e. the filesystem's) +.Sq .. +directories instead of +.Dq logical +directories (i.e. the shell handles +.Sq .. , +which allows the user to be oblivious of symbolic links to directories). +Clear by default. +Note that setting this option does not affect the current value of the +.Ev PWD +parameter; only the +.Ic cd +command changes +.Ev PWD . +See the +.Ic cd +and +.Ic pwd +commands above for more details. +.It Fl o Ic posix +Enable a somewhat more +.Px +ish mode. +As a side effect, setting this flag turns off +.Ic braceexpand +mode, which can be turned back on manually, and +.Ic sh +mode. +.It Fl o Ic sh +Enable +.Pa /bin/sh +.Pq kludge +mode. +Automatically enabled if the basename of the shell invocation begins with +.Dq sh +and this autodetection feature is compiled in +.Pq not in MirBSD . +As a side effect, setting this flag turns off +.Ic braceexpand +mode, which can be turned back on manually, and +.Ic posix +mode. +.It Fl o Ic vi +Enable +.Xr vi 1 Ns -like +command-line editing (interactive shells only). +.It Fl o Ic vi\-esccomplete +In vi command-line editing, do command and file name completion when escape +(\*(ha[) is entered in command mode. +.It Fl o Ic vi\-tabcomplete +In vi command-line editing, do command and file name completion when tab (\*(haI) +is entered in insert mode. +This is the default. +.It Fl o Ic viraw +No effect. +In the original Korn shell, unless +.Ic viraw +was set, the vi command-line mode would let the +.Xr tty 4 +driver do the work until ESC (\*(ha[) was entered. +.Nm +is always in viraw mode. +.El +.Pp +These options can also be used upon invocation of the shell. +The current set of +options (with single letter names) can be found in the parameter +.Sq $\- . +.Ic set Fl o +with no option name will list all the options and whether each is on or off; +.Ic set +o +will print the long names of all options that are currently on. +.Pp +Remaining arguments, if any, are positional parameters and are assigned, in +order, to the positional parameters (i.e. $1, $2, etc.). +If options end with +.Ql \-\- +and there are no remaining arguments, all positional parameters are cleared. +If no options or arguments are given, the values of all names are printed. +For unknown historical reasons, a lone +.Ql \- +option is treated specially \*(en it clears both the +.Fl v +and +.Fl x +options. +.Pp +.It Ic shift Op Ar number +The positional parameters +.Ar number Ns +1 , +.Ar number Ns +2 , +etc. are renamed to +.Sq 1 , +.Sq 2 , +etc. +.Ar number +defaults to 1. +.Pp +.It Ic sleep Ar seconds +Suspends execution for a minimum of the +.Ar seconds +specified as positive decimal value with an optional fractional part. +Signal delivery may continue execution earlier. +.Pp +.It Ic source Ar file Op Ar arg ... +Like +.Ic \&. Po Do dot Dc Pc , +except that the current working directory is appended to the +.Ev PATH +in GNU +.Nm bash +and +.Nm mksh . +In +.Nm ksh93 +and +.Nm mksh , +this is implemented as a shell alias instead of a builtin. +.Pp +.It Ic test Ar expression +.It Ic \&[ Ar expression Ic \&] +.Ic test +evaluates the +.Ar expression +and returns zero status if true, 1 if false, or greater than 1 if there +was an error. +It is normally used as the condition command of +.Ic if +and +.Ic while +statements. +Symbolic links are followed for all +.Ar file +expressions except +.Fl h +and +.Fl L . +.Pp +The following basic expressions are available: +.Bl -tag -width 17n +.It Fl a Ar file +.Ar file +exists. +.It Fl b Ar file +.Ar file +is a block special device. +.It Fl c Ar file +.Ar file +is a character special device. +.It Fl d Ar file +.Ar file +is a directory. +.It Fl e Ar file +.Ar file +exists. +.It Fl f Ar file +.Ar file +is a regular file. +.It Fl G Ar file +.Ar file Ns 's +group is the shell's effective group ID. +.It Fl g Ar file +.Ar file Ns 's +mode has the setgid bit set. +.It Fl H Ar file +.Ar file +is a context dependent directory (only useful on HP-UX). +.It Fl h Ar file +.Ar file +is a symbolic link. +.It Fl k Ar file +.Ar file Ns 's +mode has the +.Xr sticky 8 +bit set. +.It Fl L Ar file +.Ar file +is a symbolic link. +.It Fl O Ar file +.Ar file Ns 's +owner is the shell's effective user ID. +.It Fl o Ar option +Shell +.Ar option +is set (see the +.Ic set +command above for a list of options). +As a non-standard extension, if the option starts with a +.Ql \&! , +the test is negated; the test always fails if +.Ar option +doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option +.Ar foo +exists). +The same can be achieved with [ \-o ?foo ] like in +.At +.Nm ksh93 . +.Ar option +can also be the short flag led by either +.Ql \- +or +.Ql + +.Pq no logical negation , +for example +.Ql \-x +or +.Ql +x +instead of +.Ql xtrace . +.It Fl p Ar file +.Ar file +is a named pipe. +.It Fl r Ar file +.Ar file +exists and is readable. +.It Fl S Ar file +.Ar file +is a +.Xr unix 4 Ns -domain +socket. +.It Fl s Ar file +.Ar file +is not empty. +.It Fl t Ar fd +File descriptor +.Ar fd +is a +.Xr tty 4 +device. +.It Fl u Ar file +.Ar file Ns 's +mode has the setuid bit set. +.It Fl w Ar file +.Ar file +exists and is writable. +.It Fl x Ar file +.Ar file +exists and is executable. +.It Ar file1 Fl nt Ar file2 +.Ar file1 +is newer than +.Ar file2 +or +.Ar file1 +exists and +.Ar file2 +does not. +.It Ar file1 Fl ot Ar file2 +.Ar file1 +is older than +.Ar file2 +or +.Ar file2 +exists and +.Ar file1 +does not. +.It Ar file1 Fl ef Ar file2 +.Ar file1 +is the same file as +.Ar file2 . +.It Ar string +.Ar string +has non-zero length. +.It Fl n Ar string +.Ar string +is not empty. +.It Fl z Ar string +.Ar string +is empty. +.It Ar string No = Ar string +Strings are equal. +.It Ar string No == Ar string +Strings are equal. +.It Ar string No \*(Gt Ar string +First string operand is greater than second string operand. +.It Ar string No \*(Lt Ar string +First string operand is less than second string operand. +.It Ar string No != Ar string +Strings are not equal. +.It Ar number Fl eq Ar number +Numbers compare equal. +.It Ar number Fl ne Ar number +Numbers compare not equal. +.It Ar number Fl ge Ar number +Numbers compare greater than or equal. +.It Ar number Fl gt Ar number +Numbers compare greater than. +.It Ar number Fl le Ar number +Numbers compare less than or equal. +.It Ar number Fl \< Ar number +Numbers compare less than. +.El +.Pp +The above basic expressions, in which unary operators have precedence over +binary operators, may be combined with the following operators (listed in +increasing order of precedence): +.Bd -literal -offset indent +expr \-o expr Logical OR. +expr \-a expr Logical AND. +! expr Logical NOT. +( expr ) Grouping. +.Ed +.Pp +Note that a number actually may be an arithmetic expression, such as +a mathematical term or the name of an integer variable: +.Bd -literal -offset indent +x=1; [ "x" \-eq 1 ] evaluates to true +.Ed +.Pp +Note that some special rules are applied (courtesy of POSIX) +if the number of +arguments to +.Ic test +or +.Ic \&[ ... \&] +is less than five: if leading +.Ql \&! +arguments can be stripped such that only one argument remains then a string +length test is performed (again, even if the argument is a unary operator); if +leading +.Ql \&! +arguments can be stripped such that three arguments remain and the second +argument is a binary operator, then the binary operation is performed (even +if the first argument is a unary operator, including an unstripped +.Ql \&! ) . +.Pp +.Sy Note : +A common mistake is to use +.Dq if \&[ $foo = bar \&] +which fails if parameter +.Dq foo +is +.Dv NULL +or unset, if it has embedded spaces (i.e.\& +.Ev IFS +octets), or if it is a unary operator like +.Sq \&! +or +.Sq Fl n . +Use tests like +.Dq if \&[ x\&"$foo\&" = x"bar" \&] +instead, or the double-bracket operator +.Dq if \&[[ $foo = bar \&]] +or, to avoid pattern matching (see +.Ic \&[[ +above): +.Dq if \&[[ $foo = "$bar" \&]] +.Pp +.It Xo +.Ic time +.Op Fl p +.Op Ar pipeline +.Xc +If a +.Ar pipeline +is given, the times used to execute the pipeline are reported. +If no pipeline +is given, then the user and system time used by the shell itself, and all the +commands it has run since it was started, are reported. +The times reported are the real time (elapsed time from start to finish), +the user CPU time (time spent running in user mode), and the system CPU time +(time spent running in kernel mode). +Times are reported to standard error; the format of the output is: +.Pp +.Dl "0m0.00s real 0m0.00s user 0m0.00s system" +.Pp +If the +.Fl p +option is given the output is slightly longer: +.Bd -literal -offset indent +real 0.00 +user 0.00 +sys 0.00 +.Ed +.Pp +It is an error to specify the +.Fl p +option unless +.Ar pipeline +is a simple command. +.Pp +Simple redirections of standard error do not affect the output of the +.Ic time +command: +.Pp +.Dl $ time sleep 1 2\*(Gtafile +.Dl $ { time sleep 1; } 2\*(Gtafile +.Pp +Times for the first command do not go to +.Dq afile , +but those of the second command do. +.Pp +.It Ic times +Print the accumulated user and system times used both by the shell +and by processes that the shell started which have exited. +The format of the output is: +.Bd -literal -offset indent +0m0.00s 0m0.00s +0m0.00s 0m0.00s +.Ed +.Pp +.It Ic trap Op Ar handler signal ... +Sets a trap handler that is to be executed when any of the specified signals are +received. +.Ar handler +is either a +.Dv NULL +string, indicating the signals are to be ignored, a minus sign +.Pq Sq \- , +indicating that the default action is to be taken for the signals (see +.Xr signal 3 ) , +or a string containing shell commands to be evaluated and executed at the first +opportunity (i.e. when the current command completes, or before printing the +next +.Ev PS1 +prompt) after receipt of one of the signals. +.Ar signal +is the name of a signal (e.g.\& +.Dv PIPE +or +.Dv ALRM ) +or the number of the signal (see the +.Ic kill \-l +command above). +.Pp +There are two special signals: +.Dv EXIT +(also known as 0) which is executed when the shell is about to exit, and +.Dv ERR , +which is executed after an error occurs (an error is something that would cause +the shell to exit if the +.Fl e +or +.Ic errexit +option were set \*(en see the +.Ic set +command above). +.Dv EXIT +handlers are executed in the environment of the last executed command. +Note +that for non-interactive shells, the trap handler cannot be changed for signals +that were ignored when the shell started. +.Pp +With no arguments, +.Ic trap +lists, as a series of +.Ic trap +commands, the current state of the traps that have been set since the shell +started. +Note that the output of +.Ic trap +cannot be usefully piped to another process (an artifact of the fact that +traps are cleared when subprocesses are created). +.Pp +The original Korn shell's +.Dv DEBUG +trap and the handling of +.Dv ERR +and +.Dv EXIT +traps in functions are not yet implemented. +.Pp +.It Ic true +A command that exits with a zero value. +.Pp +.It Xo +.Ic global +.Oo Op Ic +\-alpnrtUux +.Op Fl L Ns Op Ar n +.Op Fl R Ns Op Ar n +.Op Fl Z Ns Op Ar n +.Op Fl i Ns Op Ar n +.No \*(Ba Fl f Op Fl tux Oc +.Oo Ar name +.Op Ns = Ns Ar value +.Ar ... Oc +.Xc +.It Xo +.Ic typeset +.Oo Op Ic +\-alpnrtUux +.Op Fl LRZ Ns Op Ar n +.Op Fl i Ns Op Ar n +.No \*(Ba Fl f Op Fl tux Oc +.Oo Ar name +.Op Ns = Ns Ar value +.Ar ... Oc +.Xc +Display or set parameter attributes. +With no +.Ar name +arguments, parameter attributes are displayed; if no options are used, the +current attributes of all parameters are printed as +.Ic typeset +commands; if an option is given (or +.Ql \- +with no option letter), all parameters and their values with the specified +attributes are printed; if options are introduced with +.Ql + , +parameter values are not printed. +.Pp +If +.Ar name +arguments are given, the attributes of the named parameters are set +.Pq Ic \- +or cleared +.Pq Ic + . +Values for parameters may optionally be specified. +For +.Ar name Ns \&[*] , +the change affects the entire array, and no value may be specified. +.Pp +If +.Ic typeset +is used inside a function, any parameters specified are localised. +This is not done by the otherwise identical +.Ic global . +.Pp +When +.Fl f +is used, +.Ic typeset +operates on the attributes of functions. +As with parameters, if no +.Ar name +arguments are given, +functions are listed with their values (i.e. definitions) unless +options are introduced with +.Ql + , +in which case only the function names are reported. +.Bl -tag -width Ds +.It Fl a +Indexed array attribute. +.It Fl f +Function mode. +Display or set functions and their attributes, instead of parameters. +.It Fl i Ns Op Ar n +Integer attribute. +.Ar n +specifies the base to use when displaying the integer (if not specified, the +base given in the first assignment is used). +Parameters with this attribute may +be assigned values containing arithmetic expressions. +.It Fl L Ns Op Ar n +Left justify attribute. +.Ar n +specifies the field width. +If +.Ar n +is not specified, the current width of a parameter (or the width of its first +assigned value) is used. +Leading whitespace (and zeros, if used with the +.Fl Z +option) is stripped. +If necessary, values are either truncated or space padded +to fit the field width. +.It Fl l +Lower case attribute. +All upper case characters in values are converted to lower case. +(In the original Korn shell, this parameter meant +.Dq long integer +when used with the +.Fl i +option.) +.It Fl n +Create a bound variable (name reference): any access to the variable +.Ar name +will access the variable +.Ar value +in the current scope (this is different from +.At +.Nm ksh93 ! ) +instead. +Also different from +.At +.Nm ksh93 +is that +.Ar value +is lazily evaluated at the time +.Ar name +is accessed. +This can be used by functions to access variables whose names are +passed as parametres, instead of using +.Ic eval . +.It Fl p +Print complete +.Ic typeset +commands that can be used to re-create the attributes and values of +parameters. +.It Fl R Ns Op Ar n +Right justify attribute. +.Ar n +specifies the field width. +If +.Ar n +is not specified, the current width of a parameter (or the width of its first +assigned value) is used. +Trailing whitespace is stripped. +If necessary, values are either stripped of leading characters or space +padded to make them fit the field width. +.It Fl r +Read-only attribute. +Parameters with this attribute may not be assigned to or unset. +Once this attribute is set, it cannot be turned off. +.It Fl t +Tag attribute. +Has no meaning to the shell; provided for application use. +.Pp +For functions, +.Fl t +is the trace attribute. +When functions with the trace attribute are executed, the +.Ic xtrace +.Pq Fl x +shell option is temporarily turned on. +.It Fl U +Unsigned integer attribute. +Integers are printed as unsigned values (combine with the +.Fl i +option). +This option is not in the original Korn shell. +.It Fl u +Upper case attribute. +All lower case characters in values are converted to upper case. +(In the original Korn shell, this parameter meant +.Dq unsigned integer +when used with the +.Fl i +option which meant upper case letters would never be used for bases greater +than 10. +See the +.Fl U +option.) +.Pp +For functions, +.Fl u +is the undefined attribute. +See +.Sx Functions +above for the implications of this. +.It Fl x +Export attribute. +Parameters (or functions) are placed in the environment of +any executed commands. +Exported functions are not yet implemented. +.It Fl Z Ns Op Ar n +Zero fill attribute. +If not combined with +.Fl L , +this is the same as +.Fl R , +except zero padding is used instead of space padding. +For integers, the number instead of the base is padded. +.El +.Pp +If any of the +.\" long integer , +.Fl i , +.Fl L , +.Fl l , +.Fl R , +.Fl U , +.Fl u , +or +.Fl Z +options are changed, all others from this set are cleared, +unless they are also given on the same command line. +.Pp +.It Xo +.Ic ulimit +.Op Fl aBCcdefHiLlMmnOPpqrSsTtVvw +.Op Ar value +.Xc +Display or set process limits. +If no options are used, the file size limit +.Pq Fl f +is assumed. +.Ar value , +if specified, may be either an arithmetic expression or the word +.Dq unlimited . +The limits affect the shell and any processes created by the shell after a +limit is imposed. +Note that some systems may not allow limits to be increased +once they are set. +Also note that the types of limits available are system +dependent \*(en some systems have only the +.Fl f +limit. +.Bl -tag -width 5n +.It Fl a +Display all limits; unless +.Fl H +is used, soft limits are displayed. +.It Fl B Ar n +Set the socket buffer size to +.Ar n +kibibytes. +.It Fl C Ar n +Set the number of cached threads to +.Ar n . +.It Fl c Ar n +Impose a size limit of +.Ar n +blocks on the size of core dumps. +.It Fl d Ar n +Impose a size limit of +.Ar n +kibibytes on the size of the data area. +.It Fl e Ar n +Set the maximum niceness to +.Ar n . +.It Fl f Ar n +Impose a size limit of +.Ar n +blocks on files written by the shell and its child processes (files of any +size may be read). +.It Fl H +Set the hard limit only (the default is to set both hard and soft limits). +.It Fl i Ar n +Set the number of pending signals to +.Ar n . +.It Fl L Ar n +Control flocks; documentation is missing. +.It Fl l Ar n +Impose a limit of +.Ar n +kibibytes on the amount of locked (wired) physical memory. +.It Fl M Ar n +Set the AIO locked memory to +.Ar n +kibibytes. +.It Fl m Ar n +Impose a limit of +.Ar n +kibibytes on the amount of physical memory used. +.It Fl n Ar n +Impose a limit of +.Ar n +file descriptors that can be open at once. +.It Fl O Ar n +Set the number of AIO operations to +.Ar n . +.It Fl P Ar n +Limit the number of threads per process to +.Ar n . +.It Fl p Ar n +Impose a limit of +.Ar n +processes that can be run by the user at any one time. +.It Fl q Ar n +Limit the size of +.Tn POSIX +message queues to +.Ar n +bytes. +.It Fl r Ar n +Set the maximum real-time priority to +.Ar n . +.It Fl S +Set the soft limit only (the default is to set both hard and soft limits). +.It Fl s Ar n +Impose a size limit of +.Ar n +kibibytes on the size of the stack area. +.It Fl T Ar n +Impose a time limit of +.Ar n +real seconds to be used by each process. +.It Fl t Ar n +Impose a time limit of +.Ar n +CPU seconds spent in user mode to be used by each process. +.It Fl V Ar n +Set the number of vnode monitors on Haiku to +.Ar n . +.It Fl v Ar n +Impose a limit of +.Ar n +kibibytes on the amount of virtual memory (address space) used. +.It Fl w Ar n +Impose a limit of +.Ar n +kibibytes on the amount of swap space used. +.El +.Pp +As far as +.Ic ulimit +is concerned, a block is 512 bytes. +.Pp +.It Xo +.Ic umask +.Op Fl S +.Op Ar mask +.Xc +Display or set the file permission creation mask, or umask (see +.Xr umask 2 ) . +If the +.Fl S +option is used, the mask displayed or set is symbolic; otherwise, it is an +octal number. +.Pp +Symbolic masks are like those used by +.Xr chmod 1 . +When used, they describe what permissions may be made available (as opposed to +octal masks in which a set bit means the corresponding bit is to be cleared). +For example, +.Dq ug=rwx,o= +sets the mask so files will not be readable, writable, or executable by +.Dq others , +and is equivalent (on most systems) to the octal mask +.Dq 007 . +.Pp +.It Xo +.Ic unalias +.Op Fl adt +.Op Ar name ... +.Xc +The aliases for the given names are removed. +If the +.Fl a +option is used, all aliases are removed. +If the +.Fl t +or +.Fl d +options are used, the indicated operations are carried out on tracked or +directory aliases, respectively. +.Pp +.It Xo +.Ic unset +.Op Fl fv +.Ar parameter ... +.Xc +Unset the named parameters +.Po +.Fl v , +the default +.Pc +or functions +.Pq Fl f . +With +.Ar parameter Ns \&[*] , +attributes are kept, only values are unset. +.Pp +The exit status is non-zero if any of the parameters have the read-only +attribute set, zero otherwise. +.Pp +.It Ic wait Op Ar job ... +Wait for the specified job(s) to finish. +The exit status of +.Ic wait +is that of the last specified job; if the last job is killed by a signal, the +exit status is 128 + the number of the signal (see +.Ic kill \-l Ar exit-status +above); if the last specified job can't be found (because it never existed, or +had already finished), the exit status of +.Ic wait +is 127. +See +.Sx Job control +below for the format of +.Ar job . +.Ic wait +will return if a signal for which a trap has been set is received, or if a +.Dv SIGHUP , +.Dv SIGINT , +or +.Dv SIGQUIT +signal is received. +.Pp +If no jobs are specified, +.Ic wait +waits for all currently running jobs (if any) to finish and exits with a zero +status. +If job monitoring is enabled, the completion status of jobs is printed +(this is not the case when jobs are explicitly specified). +.Pp +.It Xo +.Ic whence +.Op Fl pv +.Op Ar name ... +.Xc +For each +.Ar name , +the type of command is listed (reserved word, built-in, alias, +function, tracked alias, or executable). +If the +.Fl p +option is used, a path search is performed even if +.Ar name +is a reserved word, alias, etc. +Without the +.Fl v +option, +.Ic whence +is similar to +.Ic command Fl v +except that +.Ic whence +will find reserved words and won't print aliases as alias commands. +With the +.Fl v +option, +.Ic whence +is the same as +.Ic command Fl V . +Note that for +.Ic whence , +the +.Fl p +option does not affect the search path used, as it does for +.Ic command . +If the type of one or more of the names could not be determined, the exit +status is non-zero. +.El +.Ss Job control +Job control refers to the shell's ability to monitor and control jobs which +are processes or groups of processes created for commands or pipelines. +At a minimum, the shell keeps track of the status of the background (i.e.\& +asynchronous) jobs that currently exist; this information can be displayed +using the +.Ic jobs +commands. +If job control is fully enabled (using +.Ic set \-m +or +.Ic set \-o monitor ) , +as it is for interactive shells, the processes of a job are placed in their +own process group. +Foreground jobs can be stopped by typing the suspend +character from the terminal (normally \*(haZ), jobs can be restarted in either the +foreground or background using the +.Ic fg +and +.Ic bg +commands, and the state of the terminal is saved or restored when a foreground +job is stopped or restarted, respectively. +.Pp +Note that only commands that create processes (e.g. asynchronous commands, +subshell commands, and non-built-in, non-function commands) can be stopped; +commands like +.Ic read +cannot be. +.Pp +When a job is created, it is assigned a job number. +For interactive shells, this number is printed inside +.Dq \&[..] , +followed by the process IDs of the processes in the job when an asynchronous +command is run. +A job may be referred to in the +.Ic bg , +.Ic fg , +.Ic jobs , +.Ic kill , +and +.Ic wait +commands either by the process ID of the last process in the command pipeline +(as stored in the +.Ic $!\& +parameter) or by prefixing the job number with a percent +sign +.Pq Sq % . +Other percent sequences can also be used to refer to jobs: +.Bl -tag -width "%+ x %% x %XX" +.It %+ \*(Ba %% \*(Ba % +The most recently stopped job, or, if there are no stopped jobs, the oldest +running job. +.It %\- +The job that would be the +.Ic %+ +job if the latter did not exist. +.It % Ns Ar n +The job with job number +.Ar n . +.It %? Ns Ar string +The job with its command containing the string +.Ar string +(an error occurs if multiple jobs are matched). +.It % Ns Ar string +The job with its command starting with the string +.Ar string +(an error occurs if multiple jobs are matched). +.El +.Pp +When a job changes state (e.g. a background job finishes or foreground job is +stopped), the shell prints the following status information: +.Pp +.D1 [ Ns Ar number ] Ar flag status command +.Pp +where... +.Bl -tag -width "command" +.It Ar number +is the job number of the job; +.It Ar flag +is the +.Ql + +or +.Ql \- +character if the job is the +.Ic %+ +or +.Ic %\- +job, respectively, or space if it is neither; +.It Ar status +indicates the current state of the job and can be: +.Bl -tag -width "RunningXX" +.It Done Op Ar number +The job exited. +.Ar number +is the exit status of the job which is omitted if the status is zero. +.It Running +The job has neither stopped nor exited (note that running does not necessarily +mean consuming CPU time \*(en +the process could be blocked waiting for some event). +.It Stopped Op Ar signal +The job was stopped by the indicated +.Ar signal +(if no signal is given, the job was stopped by +.Dv SIGTSTP ) . +.It Ar signal-description Op Dq core dumped +The job was killed by a signal (e.g. memory fault, hangup); use +.Ic kill \-l +for a list of signal descriptions. +The +.Dq core dumped +message indicates the process created a core file. +.El +.It Ar command +is the command that created the process. +If there are multiple processes in +the job, each process will have a line showing its +.Ar command +and possibly its +.Ar status , +if it is different from the status of the previous process. +.El +.Pp +When an attempt is made to exit the shell while there are jobs in the stopped +state, the shell warns the user that there are stopped jobs and does not exit. +If another attempt is immediately made to exit the shell, the stopped jobs are +sent a +.Dv SIGHUP +signal and the shell exits. +Similarly, if the +.Ic nohup +option is not set and there are running jobs when an attempt is made to exit +a login shell, the shell warns the user and does not exit. +If another attempt +is immediately made to exit the shell, the running jobs are sent a +.Dv SIGHUP +signal and the shell exits. +.Ss Interactive input line editing +The shell supports three modes of reading command lines from a +.Xr tty 4 +in an interactive session, controlled by the +.Ic emacs , +.Ic gmacs , +and +.Ic vi +options (at most one of these can be set at once). +The default is +.Ic emacs . +Editing modes can be set explicitly using the +.Ic set +built-in. +If none of these options are enabled, +the shell simply reads lines using the normal +.Xr tty 4 +driver. +If the +.Ic emacs +or +.Ic gmacs +option is set, the shell allows emacs-like editing of the command; similarly, +if the +.Ic vi +option is set, the shell allows vi-like editing of the command. +These modes are described in detail in the following sections. +.Pp +In these editing modes, if a line is longer than the screen width (see the +.Ev COLUMNS +parameter), +a +.Ql \*(Gt , +.Ql + , +or +.Ql \*(Lt +character is displayed in the last column indicating that there are more +characters after, before and after, or before the current position, +respectively. +The line is scrolled horizontally as necessary. +.Pp +Completed lines are pushed into the history, unless they begin with an +IFS octet or IFS white space, or are the same as the previous line. +.Ss Emacs editing mode +When the +.Ic emacs +option is set, interactive input line editing is enabled. +Warning: This mode is +slightly different from the emacs mode in the original Korn shell. +In this mode, various editing commands +(typically bound to one or more control characters) cause immediate actions +without waiting for a newline. +Several editing commands are bound to particular +control characters when the shell is invoked; these bindings can be changed +using the +.Ic bind +command. +.Pp +The following is a list of available editing commands. +Each description starts with the name of the command, +suffixed with a colon; +an +.Op Ar n +(if the command can be prefixed with a count); and any keys the command is +bound to by default, written using caret notation +e.g. the ASCII ESC character is written as \*(ha[. +These control sequences are not case sensitive. +A count prefix for a command is entered using the sequence +.Pf \*(ha[ Ns Ar n , +where +.Ar n +is a sequence of 1 or more digits. +Unless otherwise specified, if a count is +omitted, it defaults to 1. +.Pp +Note that editing command names are used only with the +.Ic bind +command. +Furthermore, many editing commands are useful only on terminals with +a visible cursor. +The default bindings were chosen to resemble corresponding +Emacs key bindings. +The user's +.Xr tty 4 +characters (e.g.\& +.Dv ERASE ) +are bound to +reasonable substitutes and override the default bindings. +.Bl -tag -width Ds +.It abort: \*(haC, \*(haG +Abort the current command, empty the line buffer and +set the exit state to interrupted. +.It auto\-insert: Op Ar n +Simply causes the character to appear as literal input. +Most ordinary characters are bound to this. +.It Xo backward\-char: +.Op Ar n +.No \*(haB , \*(haXD , ANSI-CurLeft +.Xc +Moves the cursor backward +.Ar n +characters. +.It Xo backward\-word: +.Op Ar n +.No \*(ha[b , ANSI-Ctrl-CurLeft , ANSI-Alt-CurLeft +.Xc +Moves the cursor backward to the beginning of the word; words consist of +alphanumerics, underscore +.Pq Sq _ , +and dollar sign +.Pq Sq $ +characters. +.It beginning\-of\-history: \*(ha[\*(Lt +Moves to the beginning of the history. +.It beginning\-of\-line: \*(haA, ANSI-Home +Moves the cursor to the beginning of the edited input line. +.It Xo capitalise\-word: +.Op Ar n +.No \*(ha[C , \*(ha[c +.Xc +Uppercase the first character in the next +.Ar n +words, leaving the cursor past the end of the last word. +.It clear\-screen: \*(ha[\*(haL +Prints a compile-time configurable sequence to clear the screen and home +the cursor, redraws the entire prompt and the currently edited input line. +The default sequence works for almost all standard terminals. +.It comment: \*(ha[# +If the current line does not begin with a comment character, one is added at +the beginning of the line and the line is entered (as if return had been +pressed); otherwise, the existing comment characters are removed and the cursor +is placed at the beginning of the line. +.It complete: \*(ha[\*(ha[ +Automatically completes as much as is unique of the command name or the file +name containing the cursor. +If the entire remaining command or file name is +unique, a space is printed after its completion, unless it is a directory name +in which case +.Ql / +is appended. +If there is no command or file name with the current partial word +as its prefix, a bell character is output (usually causing a beep to be +sounded). +.It complete\-command: \*(haX\*(ha[ +Automatically completes as much as is unique of the command name having the +partial word up to the cursor as its prefix, as in the +.Ic complete +command above. +.It complete\-file: \*(ha[\*(haX +Automatically completes as much as is unique of the file name having the +partial word up to the cursor as its prefix, as in the +.Ic complete +command described above. +.It complete\-list: \*(haI, \*(ha[= +Complete as much as is possible of the current word, +and list the possible completions for it. +If only one completion is possible, +match as in the +.Ic complete +command above. +Note that \*(haI is usually generated by the TAB (tabulator) key. +.It Xo delete\-char\-backward: +.Op Ar n +.No ERASE , \*(ha? , \*(haH +.Xc +Deletes +.Ar n +characters before the cursor. +.It Xo delete\-char\-forward: +.Op Ar n +.No ANSI-Del +.Xc +Deletes +.Ar n +characters after the cursor. +.It Xo delete\-word\-backward: +.Op Ar n +.No WERASE , \*(ha[\*(ha? , \*(ha[\*(haH , \*(ha[h +.Xc +Deletes +.Ar n +words before the cursor. +.It Xo delete\-word\-forward: +.Op Ar n +.No \*(ha[d +.Xc +Deletes characters after the cursor up to the end of +.Ar n +words. +.It Xo down\-history: +.Op Ar n +.No \*(haN , \*(haXB , ANSI-CurDown +.Xc +Scrolls the history buffer forward +.Ar n +lines (later). +Each input line originally starts just after the last entry +in the history buffer, so +.Ic down\-history +is not useful until either +.Ic search\-history , +.Ic search\-history\-up +or +.Ic up\-history +has been performed. +.It Xo downcase\-word: +.Op Ar n +.No \*(ha[L , \*(ha[l +.Xc +Lowercases the next +.Ar n +words. +.It Xo edit\-line: +.Op Ar n +.No \*(haXe +.Xc +Edit line +.Ar n +or the current line, if not specified, interactively. +The actual command executed is +.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n . +.It end\-of\-history: \*(ha[\*(Gt +Moves to the end of the history. +.It end\-of\-line: \*(haE, ANSI-End +Moves the cursor to the end of the input line. +.It eot: \*(ha_ +Acts as an end-of-file; this is useful because edit-mode input disables +normal terminal input canonicalization. +.It Xo eot\-or\-delete: +.Op Ar n +.No \*(haD +.Xc +Acts as +.Ic eot +if alone on a line; otherwise acts as +.Ic delete\-char\-forward . +.It error: (not bound) +Error (ring the bell). +.It exchange\-point\-and\-mark: \*(haX\*(haX +Places the cursor where the mark is and sets the mark to where the cursor was. +.It expand\-file: \*(ha[* +Appends a +.Ql * +to the current word and replaces the word with the result of performing file +globbing on the word. +If no files match the pattern, the bell is rung. +.It Xo forward\-char: +.Op Ar n +.No \*(haF , \*(haXC , ANSI-CurRight +.Xc +Moves the cursor forward +.Ar n +characters. +.It Xo forward\-word: +.Op Ar n +.No \*(ha[f , ANSI-Ctrl-CurRight , ANSI-Alt-CurRight +.Xc +Moves the cursor forward to the end of the +.Ar n Ns th +word. +.It Xo goto\-history: +.Op Ar n +.No \*(ha[g +.Xc +Goes to history number +.Ar n . +.It kill\-line: KILL +Deletes the entire input line. +.It kill\-region: \*(haW +Deletes the input between the cursor and the mark. +.It Xo kill\-to\-eol: +.Op Ar n +.No \*(haK +.Xc +Deletes the input from the cursor to the end of the line if +.Ar n +is not specified; otherwise deletes characters between the cursor and column +.Ar n . +.It list: \*(ha[? +Prints a sorted, columnated list of command names or file names (if any) that +can complete the partial word containing the cursor. +Directory names have +.Ql / +appended to them. +.It list\-command: \*(haX? +Prints a sorted, columnated list of command names (if any) that can complete +the partial word containing the cursor. +.It list\-file: \*(haX\*(haY +Prints a sorted, columnated list of file names (if any) that can complete the +partial word containing the cursor. +File type indicators are appended as described under +.Ic list +above. +.It newline: \*(haJ , \*(haM +Causes the current input line to be processed by the shell. +The current cursor position may be anywhere on the line. +.It newline\-and\-next: \*(haO +Causes the current input line to be processed by the shell, and the next line +from history becomes the current line. +This is only useful after an +.Ic up\-history , +.Ic search\-history +or +.Ic search\-history\-up . +.It no\-op: QUIT +This does nothing. +.It prefix\-1: \*(ha[ +Introduces a 2-character command sequence. +.It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O +Introduces a 2-character command sequence. +.It Xo prev\-hist\-word: +.Op Ar n +.No \*(ha[. , \*(ha[_ +.Xc +The last word, or, if given, the +.Ar n Ns th +word (zero-based) of the previous (on repeated execution, second-last, +third-last, etc.) command is inserted at the cursor. +Use of this editing command trashes the mark. +.It quote: \*(ha\*(ha , \*(haV +The following character is taken literally rather than as an editing command. +.It redraw: \*(haL +Reprints the last line of the prompt string and the current input line +on a new line. +.It Xo search\-character\-backward: +.Op Ar n +.No \*(ha[\*(ha] +.Xc +Search backward in the current line for the +.Ar n Ns th +occurrence of the next character typed. +.It Xo search\-character\-forward: +.Op Ar n +.No \*(ha] +.Xc +Search forward in the current line for the +.Ar n Ns th +occurrence of the next character typed. +.It search\-history: \*(haR +Enter incremental search mode. +The internal history list is searched +backwards for commands matching the input. +An initial +.Ql \*(ha +in the search string anchors the search. +The escape key will leave search mode. +Other commands, including sequences of escape as +.Ic prefix\-1 +followed by a +.Ic prefix\-1 +or +.Ic prefix\-2 +key will be executed after leaving search mode. +The +.Ic abort Pq \*(haG +command will restore the input line before search started. +Successive +.Ic search\-history +commands continue searching backward to the next previous occurrence of the +pattern. +The history buffer retains only a finite number of lines; the oldest +are discarded as necessary. +.It search\-history\-up: ANSI-PgUp +Search backwards through the history buffer for commands whose beginning match +the portion of the input line before the cursor. +When used on an empty line, this has the same effect as +.Ic up\-history . +.It search\-history\-down: ANSI-PgDn +Search forwards through the history buffer for commands whose beginning match +the portion of the input line before the cursor. +When used on an empty line, this has the same effect as +.Ic down\-history . +This is only useful after an +.Ic up\-history , +.Ic search\-history +or +.Ic search\-history\-up . +.It set\-mark\-command: \*(ha[ Ns Aq space +Set the mark at the cursor position. +.It transpose\-chars: \*(haT +If at the end of line, or if the +.Ic gmacs +option is set, this exchanges the two previous characters; otherwise, it +exchanges the previous and current characters and moves the cursor one +character to the right. +.It Xo up\-history: +.Op Ar n +.No \*(haP , \*(haXA , ANSI-CurUp +.Xc +Scrolls the history buffer backward +.Ar n +lines (earlier). +.It Xo upcase\-word: +.Op Ar n +.No \*(ha[U , \*(ha[u +.Xc +Uppercase the next +.Ar n +words. +.It version: \*(ha[\*(haV +Display the version of +.Nm mksh . +The current edit buffer is restored as soon as a key is pressed. +The restoring keypress is processed, unless it is a space. +.It yank: \*(haY +Inserts the most recently killed text string at the current cursor position. +.It yank\-pop: \*(ha[y +Immediately after a +.Ic yank , +replaces the inserted text string with the next previously killed text string. +.El +.Ss Vi editing mode +.Em Note: +The vi command-line editing mode is orphaned, yet still functional. +.Pp +The vi command-line editor in +.Nm +has basically the same commands as the +.Xr vi 1 +editor with the following exceptions: +.Bl -bullet +.It +You start out in insert mode. +.It +There are file name and command completion commands: +=, \e, *, \*(haX, \*(haE, \*(haF, and, optionally, +.Aq tab +and +.Aq esc . +.It +The +.Ic _ +command is different (in +.Nm mksh , +it is the last argument command; in +.Xr vi 1 +it goes to the start of the current line). +.It +The +.Ic / +and +.Ic G +commands move in the opposite direction to the +.Ic j +command. +.It +Commands which don't make sense in a single line editor are not available +(e.g. screen movement commands and +.Xr ex 1 Ns -style +colon +.Pq Ic \&: +commands). +.El +.Pp +Like +.Xr vi 1 , +there are two modes: +.Dq insert +mode and +.Dq command +mode. +In insert mode, most characters are simply put in the buffer at the +current cursor position as they are typed; however, some characters are +treated specially. +In particular, the following characters are taken from current +.Xr tty 4 +settings +(see +.Xr stty 1 ) +and have their usual meaning (normal values are in parentheses): kill (\*(haU), +erase (\*(ha?), werase (\*(haW), eof (\*(haD), intr (\*(haC), and quit (\*(ha\e). +In addition to +the above, the following characters are also treated specially in insert mode: +.Bl -tag -width XJXXXXM +.It \*(haE +Command and file name enumeration (see below). +.It \*(haF +Command and file name completion (see below). +If used twice in a row, the +list of possible completions is displayed; if used a third time, the completion +is undone. +.It \*(haH +Erases previous character. +.It \*(haJ \*(Ba \*(haM +End of line. +The current line is read, parsed, and executed by the shell. +.It \*(haV +Literal next. +The next character typed is not treated specially (can be used +to insert the characters being described here). +.It \*(haX +Command and file name expansion (see below). +.It Aq esc +Puts the editor in command mode (see below). +.It Aq tab +Optional file name and command completion (see +.Ic \*(haF +above), enabled with +.Ic set \-o vi\-tabcomplete . +.El +.Pp +In command mode, each character is interpreted as a command. +Characters that +don't correspond to commands, are illegal combinations of commands, or are +commands that can't be carried out, all cause beeps. +In the following command descriptions, an +.Op Ar n +indicates the command may be prefixed by a number (e.g.\& +.Ic 10l +moves right 10 characters); if no number prefix is used, +.Ar n +is assumed to be 1 unless otherwise specified. +The term +.Dq current position +refers to the position between the cursor and the character preceding the +cursor. +A +.Dq word +is a sequence of letters, digits, and underscore characters or a sequence of +non-letter, non-digit, non-underscore, and non-whitespace characters (e.g.\& +.Dq ab2*&\*(ha +contains two words) and a +.Dq big-word +is a sequence of non-whitespace characters. +.Pp +Special +.Nm +vi commands: +.Pp +The following commands are not in, or are different from, the normal vi file +editor: +.Bl -tag -width 10n +.It Xo +.Oo Ar n Oc Ns _ +.Xc +Insert a space followed by the +.Ar n Ns th +big-word from the last command in the history at the current position and enter +insert mode; if +.Ar n +is not specified, the last word is inserted. +.It # +Insert the comment character +.Pq Sq # +at the start of the current line and return the line to the shell (equivalent +to +.Ic I#\*(haJ ) . +.It Xo +.Oo Ar n Oc Ns g +.Xc +Like +.Ic G , +except if +.Ar n +is not specified, it goes to the most recent remembered line. +.It Xo +.Oo Ar n Oc Ns v +.Xc +Edit line +.Ar n +using the +.Xr vi 1 +editor; if +.Ar n +is not specified, the current line is edited. +The actual command executed is +.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n . +.It * and \*(haX +Command or file name expansion is applied to the current big-word (with an +appended +.Ql * +if the word contains no file globbing characters) \*(en the big-word is replaced +with the resulting words. +If the current big-word is the first on the line +or follows one of the characters +.Ql \&; , +.Ql \*(Ba , +.Ql & , +.Ql \&( , +or +.Ql \&) , +and does not contain a slash +.Pq Sq / , +then command expansion is done; otherwise file name expansion is done. +Command expansion will match the big-word against all aliases, functions, and +built-in commands as well as any executable files found by searching the +directories in the +.Ev PATH +parameter. +File name expansion matches the big-word against the files in the +current directory. +After expansion, the cursor is placed just past the last +word and the editor is in insert mode. +.It Xo +.Oo Ar n Oc Ns \e , +.Oo Ar n Oc Ns \*(haF , +.Oo Ar n Oc Ns Aq tab , +.No and +.Oo Ar n Oc Ns Aq esc +.Xc +Command/file name completion. +Replace the current big-word with the +longest unique match obtained after performing command and file name expansion. +.Aq tab +is only recognised if the +.Ic vi\-tabcomplete +option is set, while +.Aq esc +is only recognised if the +.Ic vi\-esccomplete +option is set (see +.Ic set \-o ) . +If +.Ar n +is specified, the +.Ar n Ns th +possible completion is selected (as reported by the command/file name +enumeration command). +.It = and \*(haE +Command/file name enumeration. +List all the commands or files that match the current big-word. +.It \*(haV +Display the version of +.Nm mksh . +The current edit buffer is restored as soon as a key is pressed. +The restoring keypress is ignored. +.It @ Ns Ar c +Macro expansion. +Execute the commands found in the alias +.Ar c . +.El +.Pp +Intra-line movement commands: +.Bl -tag -width Ds +.It Xo +.Oo Ar n Oc Ns h and +.Oo Ar n Oc Ns \*(haH +.Xc +Move left +.Ar n +characters. +.It Xo +.Oo Ar n Oc Ns l and +.Oo Ar n Oc Ns Aq space +.Xc +Move right +.Ar n +characters. +.It 0 +Move to column 0. +.It \*(ha +Move to the first non-whitespace character. +.It Xo +.Oo Ar n Oc Ns \*(Ba +.Xc +Move to column +.Ar n . +.It $ +Move to the last character. +.It Xo +.Oo Ar n Oc Ns b +.Xc +Move back +.Ar n +words. +.It Xo +.Oo Ar n Oc Ns B +.Xc +Move back +.Ar n +big-words. +.It Xo +.Oo Ar n Oc Ns e +.Xc +Move forward to the end of the word, +.Ar n +times. +.It Xo +.Oo Ar n Oc Ns E +.Xc +Move forward to the end of the big-word, +.Ar n +times. +.It Xo +.Oo Ar n Oc Ns w +.Xc +Move forward +.Ar n +words. +.It Xo +.Oo Ar n Oc Ns W +.Xc +Move forward +.Ar n +big-words. +.It % +Find match. +The editor looks forward for the nearest parenthesis, bracket, or +brace and then moves the cursor to the matching parenthesis, bracket, or brace. +.It Xo +.Oo Ar n Oc Ns f Ns Ar c +.Xc +Move forward to the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Xo +.Oo Ar n Oc Ns F Ns Ar c +.Xc +Move backward to the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Xo +.Oo Ar n Oc Ns t Ns Ar c +.Xc +Move forward to just before the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Xo +.Oo Ar n Oc Ns T Ns Ar c +.Xc +Move backward to just before the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Xo +.Oo Ar n Oc Ns \&; +.Xc +Repeats the last +.Ic f , F , t , +or +.Ic T +command. +.It Xo +.Oo Ar n Oc Ns \&, +.Xc +Repeats the last +.Ic f , F , t , +or +.Ic T +command, but moves in the opposite direction. +.El +.Pp +Inter-line movement commands: +.Bl -tag -width Ds +.It Xo +.Oo Ar n Oc Ns j , +.Oo Ar n Oc Ns + , +.No and +.Oo Ar n Oc Ns \*(haN +.Xc +Move to the +.Ar n Ns th +next line in the history. +.It Xo +.Oo Ar n Oc Ns k , +.Oo Ar n Oc Ns \- , +.No and +.Oo Ar n Oc Ns \*(haP +.Xc +Move to the +.Ar n Ns th +previous line in the history. +.It Xo +.Oo Ar n Oc Ns G +.Xc +Move to line +.Ar n +in the history; if +.Ar n +is not specified, the number of the first remembered line is used. +.It Xo +.Oo Ar n Oc Ns g +.Xc +Like +.Ic G , +except if +.Ar n +is not specified, it goes to the most recent remembered line. +.It Xo +.Oo Ar n Oc Ns / Ns Ar string +.Xc +Search backward through the history for the +.Ar n Ns th +line containing +.Ar string ; +if +.Ar string +starts with +.Ql \*(ha , +the remainder of the string must appear at the start of the history line for +it to match. +.It Xo +.Oo Ar n Oc Ns \&? Ns Ar string +.Xc +Same as +.Ic / , +except it searches forward through the history. +.It Xo +.Oo Ar n Oc Ns n +.Xc +Search for the +.Ar n Ns th +occurrence of the last search string; +the direction of the search is the same as the last search. +.It Xo +.Oo Ar n Oc Ns N +.Xc +Search for the +.Ar n Ns th +occurrence of the last search string; +the direction of the search is the opposite of the last search. +.El +.Pp +Edit commands +.Bl -tag -width Ds +.It Xo +.Oo Ar n Oc Ns a +.Xc +Append text +.Ar n +times; goes into insert mode just after the current position. +The append is +only replicated if command mode is re-entered i.e.\& +.Aq esc +is used. +.It Xo +.Oo Ar n Oc Ns A +.Xc +Same as +.Ic a , +except it appends at the end of the line. +.It Xo +.Oo Ar n Oc Ns i +.Xc +Insert text +.Ar n +times; goes into insert mode at the current position. +The insertion is only +replicated if command mode is re-entered i.e.\& +.Aq esc +is used. +.It Xo +.Oo Ar n Oc Ns I +.Xc +Same as +.Ic i , +except the insertion is done just before the first non-blank character. +.It Xo +.Oo Ar n Oc Ns s +.Xc +Substitute the next +.Ar n +characters (i.e. delete the characters and go into insert mode). +.It S +Substitute whole line. +All characters from the first non-blank character to the +end of the line are deleted and insert mode is entered. +.It Xo +.Oo Ar n Oc Ns c Ns Ar move-cmd +.Xc +Change from the current position to the position resulting from +.Ar n move-cmd Ns s +(i.e. delete the indicated region and go into insert mode); if +.Ar move-cmd +is +.Ic c , +the line starting from the first non-blank character is changed. +.It C +Change from the current position to the end of the line (i.e. delete to the +end of the line and go into insert mode). +.It Xo +.Oo Ar n Oc Ns x +.Xc +Delete the next +.Ar n +characters. +.It Xo +.Oo Ar n Oc Ns X +.Xc +Delete the previous +.Ar n +characters. +.It D +Delete to the end of the line. +.It Xo +.Oo Ar n Oc Ns d Ns Ar move-cmd +.Xc +Delete from the current position to the position resulting from +.Ar n move-cmd Ns s ; +.Ar move-cmd +is a movement command (see above) or +.Ic d , +in which case the current line is deleted. +.It Xo +.Oo Ar n Oc Ns r Ns Ar c +.Xc +Replace the next +.Ar n +characters with the character +.Ar c . +.It Xo +.Oo Ar n Oc Ns R +.Xc +Replace. +Enter insert mode but overwrite existing characters instead of +inserting before existing characters. +The replacement is repeated +.Ar n +times. +.It Xo +.Oo Ar n Oc Ns \*(TI +.Xc +Change the case of the next +.Ar n +characters. +.It Xo +.Oo Ar n Oc Ns y Ns Ar move-cmd +.Xc +Yank from the current position to the position resulting from +.Ar n move-cmd Ns s +into the yank buffer; if +.Ar move-cmd +is +.Ic y , +the whole line is yanked. +.It Y +Yank from the current position to the end of the line. +.It Xo +.Oo Ar n Oc Ns p +.Xc +Paste the contents of the yank buffer just after the current position, +.Ar n +times. +.It Xo +.Oo Ar n Oc Ns P +.Xc +Same as +.Ic p , +except the buffer is pasted at the current position. +.El +.Pp +Miscellaneous vi commands +.Bl -tag -width Ds +.It \*(haJ and \*(haM +The current line is read, parsed, and executed by the shell. +.It \*(haL and \*(haR +Redraw the current line. +.It Xo +.Oo Ar n Oc Ns \&. +.Xc +Redo the last edit command +.Ar n +times. +.It u +Undo the last edit command. +.It U +Undo all changes that have been made to the current line. +.It Ar intr No and Ar quit +The interrupt and quit terminal characters cause the current line to be +deleted and a new prompt to be printed. +.El +.Sh FILES +.Bl -tag -width XetcXsuid_profile -compact +.It Pa \*(TI/.mkshrc +User mkshrc profile (non-privileged interactive shells); see +.Sx Startup files. +The location can be changed at compile time (for embedded systems); +AOSP Android builds use +.Pa /system/etc/mkshrc . +.It Pa \*(TI/.profile +User profile (non-privileged login shells); see +.Sx Startup files +near the top of this manual. +.It Pa /etc/profile +System profile (login shells); see +.Sx Startup files. +.It Pa /etc/shells +Shell database. +.It Pa /etc/suid_profile +Suid profile (privileged shells); see +.Sx Startup files. +.El +.Pp +Note: On Android, +.Pa /system/etc/ +contains the system and suid profile. +.Sh SEE ALSO +.Xr awk 1 , +.Xr cat 1 , +.Xr ed 1 , +.Xr getopt 1 , +.Xr printf 1 , +.Xr sed 1 , +.Xr sh 1 , +.Xr stty 1 , +.Xr dup 2 , +.Xr execve 2 , +.Xr getgid 2 , +.Xr getuid 2 , +.Xr mknod 2 , +.Xr mkfifo 2 , +.Xr open 2 , +.Xr pipe 2 , +.Xr rename 2 , +.Xr wait 2 , +.Xr getopt 3 , +.Xr nl_langinfo 3 , +.Xr setlocale 3 , +.Xr signal 3 , +.Xr system 3 , +.Xr tty 4 , +.Xr shells 5 , +.Xr environ 7 , +.Xr script 7 , +.Xr utf\-8 7 , +.Xr mknod 8 +.Pp +.Pa http://docsrv.sco.com:507/en/man/html.C/sh.C.html +.Rs +.%A Morris Bolsky +.%B "The KornShell Command and Programming Language" +.%D 1989 +.%I "Prentice Hall PTR" +.%P "xvi\ +\ 356 pages" +.%O "ISBN 978\-0\-13\-516972\-8 (0\-13\-516972\-0)" +.Re +.Rs +.%A Morris I. Bolsky +.%A David G. Korn +.%B "The New KornShell Command and Programming Language (2nd Edition)" +.%D 1995 +.%I "Prentice Hall PTR" +.%P "xvi\ +\ 400 pages" +.%O "ISBN 978\-0\-13\-182700\-4 (0\-13\-182700\-6)" +.Re +.Rs +.%A Stephen G. Kochan +.%A Patrick H. Wood +.%B "\\*(tNUNIX\\*(sP Shell Programming" +.%V "Revised Edition" +.%D 1990 +.%I "Hayden" +.%P "xi\ +\ 490 pages" +.%O "ISBN 978\-0\-672\-48448\-3 (0\-672\-48448\-X)" +.Re +.Rs +.%A "IEEE Inc." +.%B "\\*(tNIEEE\\*(sP Standard for Information Technology \*(en Portable Operating System Interface (POSIX)" +.%V "Part 2: Shell and Utilities" +.%D 1993 +.%I "IEEE Press" +.%P "xvii\ +\ 1195 pages" +.%O "ISBN 978\-1\-55937\-255\-8 (1\-55937\-255\-9)" +.Re +.Rs +.%A Bill Rosenblatt +.%B "Learning the Korn Shell" +.%D 1993 +.%I "O'Reilly" +.%P "360 pages" +.%O "ISBN 978\-1\-56592\-054\-5 (1\-56592\-054\-6)" +.Re +.Rs +.%A Bill Rosenblatt +.%A Arnold Robbins +.%B "Learning the Korn Shell, Second Edition" +.%D 2002 +.%I "O'Reilly" +.%P "432 pages" +.%O "ISBN 978\-0\-596\-00195\-7 (0\-596\-00195\-9)" +.Re +.Rs +.%A Barry Rosenberg +.%B "KornShell Programming Tutorial" +.%D 1991 +.%I "Addison-Wesley Professional" +.%P "xxi\ +\ 324 pages" +.%O "ISBN 978\-0\-201\-56324\-5 (0\-201\-56324\-X)" +.Re +.Sh AUTHORS +.Nm "The MirBSD Korn Shell" +is developed by +.An Thorsten Glaser Aq tg@mirbsd.org +and currently maintained as part of The MirOS Project. +This shell is based upon the Public Domain Korn SHell. +The developer of mksh recognises the efforts of the pdksh authors, +who had dedicated their work into Public Domain, our users, and +all contributors, such as the Debian and OpenBSD projects. +.\" +.\" Charles Forsyth, author of the (Public Domain) Bourne Shell clone, +.\" which mksh is derived from, agreed to the following: +.\" +.\" In countries where the Public Domain status of the work may not be +.\" valid, its primary author hereby grants a copyright licence to the +.\" general public to deal in the work without restriction and permis- +.\" sion to sublicence derivates under the terms of any (OSI approved) +.\" Open Source licence. +.\" +See the documentation, CVS, and web site for details. +.Sh CAVEATS +.Nm +only supports the Unicode BMP (Basic Multilingual Plane). +It has a different scope model from +.At +.Nm ksh , +which leads to subtile differences in semantics for identical builtins. +.Pp +The parts of a pipeline, like below, are executed in subshells. +Thus, variable assignments inside them fail. +Use co-processes instead. +.Bd -literal -offset indent +foo \*(Ba bar \*(Ba read baz # will not change $baz +foo \*(Ba bar \*(Ba& read \-p baz # will, however, do so +.Ed +.Sh BUGS +Suspending (using \*(haZ) pipelines like the one below will only suspend +the currently running part of the pipeline; in this example, +.Dq fubar +is immediately printed on suspension (but not later after an +.Ic fg ) . +.Bd -literal -offset indent +$ /bin/sleep 666 && echo fubar +.Ed +.Pp +This document attempts to describe +.Nm mksh\ R40+CVS +and up, +compiled without any options impacting functionality, such as +.Dv MKSH_SMALL , +for an operating environment supporting all of its advanced needs. +Please report bugs in +.Nm +to the +.Mx +mailing list at +.Aq miros\-discuss@mirbsd.org +or in the +.Li \&#\&!/bin/mksh +.Pq or Li \&#ksh +IRC channel at +.Pa irc.freenode.net:6667 . @@ -9,28 +9,28 @@ /* $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices * are retained or reproduced in an accompanying document, permission - * is granted to deal in this work without restriction, including un- + * is granted to deal in this work without restriction, including un‐ * limited rights to use, publicly perform, distribute, sell, modify, * merge, give away, or sublicence. * - * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to * the utmost extent permitted by applicable law, neither express nor * implied; without malicious intent or gross negligence. In no event * may a licensor, author or contributor be held liable for indirect, * direct, other damage, loss, or other issues arising in any way out * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out - * of said person's immediate fault when using the work as intended. + * of said person’s immediate fault when using the work as intended. */ #ifdef __dietlibc__ /* XXX imake style */ -#define _BSD_SOURCE /* live, BSD, live! */ +#define _BSD_SOURCE /* live, BSD, live❣ */ #endif #if HAVE_SYS_PARAM_H @@ -68,9 +68,6 @@ #include <setjmp.h> #include <signal.h> #include <stdarg.h> -#if HAVE_STDBOOL_H -#include <stdbool.h> -#endif #include <stddef.h> #if HAVE_STDINT_H #include <stdint.h> @@ -93,12 +90,12 @@ #undef __attribute__ #if HAVE_ATTRIBUTE_BOUNDED -#define MKSH_A_BOUNDED(x,y,z) __attribute__((bounded (x, y, z))) +#define MKSH_A_BOUNDED(x,y,z) __attribute__((__bounded__ (x, y, z))) #else #define MKSH_A_BOUNDED(x,y,z) /* nothing */ #endif #if HAVE_ATTRIBUTE_FORMAT -#define MKSH_A_FORMAT(x,y,z) __attribute__((format (x, y, z))) +#define MKSH_A_FORMAT(x,y,z) __attribute__((__format__ (x, y, z))) #else #define MKSH_A_FORMAT(x,y,z) /* nothing */ #endif @@ -108,17 +105,17 @@ #define MKSH_A_NONNULL(a) /* nothing */ #endif #if HAVE_ATTRIBUTE_NORETURN -#define MKSH_A_NORETURN __attribute__((noreturn)) +#define MKSH_A_NORETURN __attribute__((__noreturn__)) #else #define MKSH_A_NORETURN /* nothing */ #endif #if HAVE_ATTRIBUTE_UNUSED -#define MKSH_A_UNUSED __attribute__((unused)) +#define MKSH_A_UNUSED __attribute__((__unused__)) #else #define MKSH_A_UNUSED /* nothing */ #endif #if HAVE_ATTRIBUTE_USED -#define MKSH_A_USED __attribute__((used)) +#define MKSH_A_USED __attribute__((__used__)) #else #define MKSH_A_USED /* nothing */ #endif @@ -141,18 +138,22 @@ #undef __SCCSID #define __IDSTRING_CONCAT(l,p) __LINTED__ ## l ## _ ## p #define __IDSTRING_EXPAND(l,p) __IDSTRING_CONCAT(l,p) +#ifdef MKSH_DONT_EMIT_IDSTRING +#define __IDSTRING(prefix, string) /* nothing */ +#else #define __IDSTRING(prefix, string) \ static const char __IDSTRING_EXPAND(__LINE__,prefix) [] \ MKSH_A_USED = "@(""#)" #prefix ": " string +#endif #define __COPYRIGHT(x) __IDSTRING(copyright,x) #define __RCSID(x) __IDSTRING(rcsid,x) #define __SCCSID(x) __IDSTRING(sccsid,x) #endif #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.495 2011/10/07 19:51:44 tg Exp $"); #endif -#define MKSH_VERSION "R39 2010/08/24" +#define MKSH_VERSION "R40 2011/10/07" #ifndef MKSH_INCLUDES_ONLY @@ -163,8 +164,8 @@ __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $"); #undef RUSAGE_SELF #undef RUSAGE_CHILDREN #define rusage mksh_rusage -#define RUSAGE_SELF 0 -#define RUSAGE_CHILDREN -1 +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN -1 struct rusage { struct timeval ru_utime; @@ -181,13 +182,6 @@ typedef long rlim_t; typedef void (*sig_t)(int); #endif -#if !HAVE_STDBOOL_H -/* kludge, but enough for mksh */ -typedef int bool; -#define false 0 -#define true 1 -#endif - #if !HAVE_CAN_INTTYPES #if !HAVE_CAN_UCBINTS typedef signed int int32_t; @@ -264,6 +258,9 @@ typedef u_int8_t uint8_t; #ifndef S_ISSOCK #define S_ISSOCK(m) ((m & 0170000) == 0140000) #endif +#if !defined(S_ISCDF) && defined(S_CDF) +#define S_ISCDF(m) (S_ISDIR(m) && ((m) & S_CDF)) +#endif #ifndef DEFFILEMODE #define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) #endif @@ -301,11 +298,6 @@ extern int getrusage(int, struct rusage *); extern int revoke(const char *); #endif -#if !HAVE_SETMODE -mode_t getmode(const void *, mode_t); -void *setmode(const char *); -#endif - #ifdef __ultrix /* XXX imake style */ int strcasecmp(const char *, const char *); @@ -341,23 +333,35 @@ extern int wcwidth(__WCHAR_TYPE__); /* some useful #defines */ #ifdef EXTERN -# define I__(i) = i +# define E_INIT(i) = i #else -# define I__(i) +# define E_INIT(i) # define EXTERN extern # define EXTERN_DEFINED #endif +/* define bit in flag */ +#define BIT(i) (1 << (i)) #define NELEM(a) (sizeof(a) / sizeof((a)[0])) -#define BIT(i) (1 << (i)) /* define bit in flag */ - -/* Table flag type - needs > 16 and < 32 bits */ -typedef int32_t Tflag; /* arithmetics types */ typedef int32_t mksh_ari_t; typedef uint32_t mksh_uari_t; +/* boolean type (no <stdbool.h> deliberately) */ +typedef unsigned char mksh_bool; +#undef bool +/* false MUST equal 0 */ +#undef false +#undef true +/* access macros for boolean type */ +#define bool mksh_bool +/* values must have identity mapping between mksh_bool and short */ +#define false 0 +#define true 1 +/* make any-type into bool or short */ +#define tobool(cond) ((cond) ? true : false) + /* these shall be smaller than 100 */ #ifdef MKSH_CONSERVATIVE_FDS #define NUFILE 32 /* Number of user-accessible files */ @@ -367,7 +371,8 @@ typedef uint32_t mksh_uari_t; #define FDBASE 24 /* First file usable by Shell */ #endif -/* Make MAGIC a char that might be printed to make bugs more obvious, but +/* + * Make MAGIC a char that might be printed to make bugs more obvious, but * not a char that is used often. Also, can't use the high bit as it causes * portability problems (calling strchr(x, 0x80|'x') is error prone). */ @@ -378,11 +383,11 @@ typedef uint32_t mksh_uari_t; #define LINE 4096 /* input line size */ EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */ -EXTERN const char initvsn[] I__("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION); +EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION); #define KSH_VERSION (initvsn + /* "KSH_VERSION=@(#)" */ 16) -EXTERN const char digits_uc[] I__("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); -EXTERN const char digits_lc[] I__("0123456789abcdefghijklmnopqrstuvwxyz"); +EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); +EXTERN const char digits_lc[] E_INIT("0123456789abcdefghijklmnopqrstuvwxyz"); /* * Evil hack for const correctness due to API brokenness @@ -471,7 +476,7 @@ char *ucstrstr(char *, const char *); #endif #if HAVE_STRCASESTR -#define stristr(b,l) ((const char *)strcasestr((b), (l))) +#define stristr(b,l) ((const char *)strcasestr((b), (l))) #endif #ifdef MKSH_SMALL @@ -490,10 +495,43 @@ char *ucstrstr(char *, const char *); #define MKSH_S_NOVI 0 #endif +#if defined(MKSH_NOPROSPECTOFWORK) && !defined(MKSH_UNEMPLOYED) +#define MKSH_UNEMPLOYED 1 +#endif + /* * simple grouping allocator */ + +/* 0. OS API: where to get memory from and how to free it (grouped) */ + +/* malloc(3)/realloc(3) -> free(3) for use by the memory allocator */ +#define malloc_osi(sz) malloc(sz) +#define realloc_osi(p,sz) realloc((p), (sz)) +#define free_osimalloc(p) free(p) + +/* malloc(3)/realloc(3) -> free(3) for use by mksh code */ +#define malloc_osfunc(sz) malloc(sz) +#define realloc_osfunc(p,sz) realloc((p), (sz)) +#define free_osfunc(p) free(p) + +#if HAVE_MKNOD +/* setmode(3) -> free(3) */ +#define free_ossetmode(p) free(p) +#endif + +#if !HAVE_MKSTEMP +/* tempnam(3) -> free(3) */ +#define free_ostempnam(p) free(p) +#endif + +#ifdef NO_PATH_MAX +/* GNU libc: get_current_dir_name(3) -> free(3) */ +#define free_gnu_gcdn(p) free(p) +#endif + + /* 1. internal structure */ struct lalloc { struct lalloc *next; @@ -520,14 +558,14 @@ enum sh_flag { FNFLAGS /* (place holder: how many flags are there) */ }; -#define Flag(f) (kshstate_v.shell_flags_[(int)(f)]) +#define Flag(f) (shell_flags[(int)(f)]) #define UTFMODE Flag(FUNICODE) /* * parsing & execution environment */ extern struct env { - ALLOC_ITEM __alloc_i; /* internal, do not touch */ + ALLOC_ITEM alloc_INT; /* internal, do not touch */ Area area; /* temporary allocation area */ struct env *oenv; /* link to previous environment */ struct block *loc; /* local variables and functions */ @@ -570,42 +608,32 @@ extern struct env { #define LSHELL 8 /* return to interactive shell() */ #define LAEXPR 9 /* error in arithmetic expression */ -/* - * some kind of global shell state, for change_random() mostly - */ - -EXTERN struct mksh_kshstate_v { - /* for change_random */ - struct timeval cr_tv; /* timestamp */ - const void *cr_dp; /* argument address */ - size_t cr_dsz; /* argument length */ - uint32_t lcg_state_; /* previous LCG state */ - /* global state */ - pid_t procpid_; /* PID of executing process */ - int exstat_; /* exit status */ - int subst_exstat_; /* exit status of last $(..)/`..` */ - struct env env_; /* top-level parsing & execution env. */ - uint8_t shell_flags_[FNFLAGS]; -} kshstate_v; -EXTERN struct mksh_kshstate_f { - const char *kshname_; /* $0 */ - pid_t kshpid_; /* $$, shell PID */ - pid_t kshpgrp_; /* process group of shell */ - uid_t ksheuid_; /* effective UID of shell */ - pid_t kshppid_; /* PID of parent of shell */ - uint32_t h; /* some kind of hash */ -} kshstate_f; -#define kshname kshstate_f.kshname_ -#define kshpid kshstate_f.kshpid_ -#define procpid kshstate_v.procpid_ -#define kshpgrp kshstate_f.kshpgrp_ -#define ksheuid kshstate_f.ksheuid_ -#define kshppid kshstate_f.kshppid_ -#define exstat kshstate_v.exstat_ -#define subst_exstat kshstate_v.subst_exstat_ - -/* evil hack: return hash(kshstate_f concat (kshstate_f'.h:=hash(arg))) */ -uint32_t evilhash(const char *); +/* sort of shell global state */ +EXTERN pid_t procpid; /* PID of executing process */ +EXTERN int exstat; /* exit status */ +EXTERN int subst_exstat; /* exit status of last $(..)/`..` */ +EXTERN struct tbl *vp_pipest; /* global PIPESTATUS array */ +EXTERN short trap_exstat; /* exit status before running a trap */ +EXTERN uint8_t trap_nested; /* running nested traps */ +EXTERN uint8_t shell_flags[FNFLAGS]; +EXTERN const char *kshname; /* $0 */ +EXTERN struct { + uid_t kshuid_v; /* real UID of shell */ + uid_t ksheuid_v; /* effective UID of shell */ + gid_t kshgid_v; /* real GID of shell */ + gid_t kshegid_v; /* effective GID of shell */ + pid_t kshpgrp_v; /* process group of shell */ + pid_t kshppid_v; /* PID of parent of shell */ + pid_t kshpid_v; /* $$, shell PID */ +} rndsetupstate; + +#define kshpid rndsetupstate.kshpid_v +#define kshpgrp rndsetupstate.kshpgrp_v +#define kshuid rndsetupstate.kshuid_v +#define ksheuid rndsetupstate.ksheuid_v +#define kshgid rndsetupstate.kshgid_v +#define kshegid rndsetupstate.kshegid_v +#define kshppid rndsetupstate.kshppid_v /* option processing */ @@ -623,16 +651,34 @@ struct shoption { }; extern const struct shoption options[]; -/* null value for variable; comparision pointer for unset */ -EXTERN char null[] I__(""); +/* null value for variable; comparison pointer for unset */ +EXTERN char null[] E_INIT(""); /* helpers for string pooling */ -#define T_synerr "syntax error" -EXTERN const char r_fc_e_[] I__("r=fc -e -"); -#define fc_e_ (r_fc_e_ + 2) /* "fc -e -" */ -#define fc_e_n 7 /* strlen(fc_e_) */ -EXTERN const char T_local_typeset[] I__("local=typeset"); -#define T__typeset (T_local_typeset + 5) /* "=typeset" */ -#define T_typeset (T_local_typeset + 6) /* "typeset" */ +EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented"); +EXTERN const char Toomem[] E_INIT("can't allocate %lu data bytes"); +#if defined(__GNUC__) +/* trust this to have string pooling; -Wformat bitches otherwise */ +#define Tsynerr "syntax error" +#else +EXTERN const char Tsynerr[] E_INIT("syntax error"); +#endif +EXTERN const char Tselect[] E_INIT("select"); +EXTERN const char Tr_fc_e_dash[] E_INIT("r=fc -e -"); +#define Tfc_e_dash (Tr_fc_e_dash + 2) /* "fc -e -" */ +#define Zfc_e_dash 7 /* strlen(Tfc_e_dash) */ +EXTERN const char Tlocal_typeset[] E_INIT("local=typeset"); +#define T_typeset (Tlocal_typeset + 5) /* "=typeset" */ +#define Ttypeset (Tlocal_typeset + 6) /* "typeset" */ +EXTERN const char Tpalias[] E_INIT("+alias"); +#define Talias (Tpalias + 1) /* "alias" */ +EXTERN const char Tpunalias[] E_INIT("+unalias"); +#define Tunalias (Tpunalias + 1) /* "unalias" */ +EXTERN const char Tsgset[] E_INIT("*=set"); +#define Tset (Tsgset + 2) /* "set" */ +EXTERN const char Tgbuiltin[] E_INIT("=builtin"); +#define Tbuiltin (Tgbuiltin + 1) /* "builtin" */ +EXTERN const char T_function[] E_INIT(" function"); +#define Tfunction (T_function + 1) /* "function" */ enum temp_type { TT_HEREDOC_EXP, /* expanded heredoc */ @@ -655,7 +701,7 @@ struct temp { #define shl_spare (&shf_iob[0]) /* for c_read()/c_print() */ #define shl_stdout (&shf_iob[1]) #define shl_out (&shf_iob[2]) -EXTERN int shl_stdout_ok; +EXTERN bool shl_stdout_ok; /* * trap handlers @@ -693,17 +739,17 @@ typedef struct trap { #define SS_USER BIT(4) /* user is doing the set (ie, trap command) */ #define SS_SHTRAP BIT(5) /* trap for internal use (ALRM, CHLD, WINCH) */ -#define SIGEXIT_ 0 /* for trap EXIT */ -#define SIGERR_ NSIG /* for trap ERR */ +#define ksh_SIGEXIT 0 /* for trap EXIT */ +#define ksh_SIGERR NSIG /* for trap ERR */ EXTERN volatile sig_atomic_t trap; /* traps pending? */ EXTERN volatile sig_atomic_t intrsig; /* pending trap interrupts command */ -EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */ +EXTERN volatile sig_atomic_t fatal_trap; /* received a fatal signal */ extern Trap sigtraps[NSIG+1]; /* got_winch = 1 when we need to re-adjust the window size */ #ifdef SIGWINCH -EXTERN volatile sig_atomic_t got_winch I__(1); +EXTERN volatile sig_atomic_t got_winch E_INIT(1); #else #define got_winch true #endif @@ -718,7 +764,7 @@ enum tmout_enum { TMOUT_LEAVING /* have timed out */ }; EXTERN unsigned int ksh_tmout; -EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING); +EXTERN enum tmout_enum ksh_tmout_state E_INIT(TMOUT_EXECUTING); /* For "You have stopped jobs" message */ EXTERN int really_exit; @@ -738,13 +784,13 @@ EXTERN int really_exit; extern unsigned char chtypes[]; -#define ctype(c, t) !!( ((t) == C_SUBOP2) ? \ +#define ctype(c, t) tobool( ((t) == C_SUBOP2) ? \ (((c) == '#' || (c) == '%') ? 1 : 0) : \ - (chtypes[(unsigned char)(c)]&(t)) ) + (chtypes[(unsigned char)(c)] & (t)) ) #define ksh_isalphx(c) ctype((c), C_ALPHA) #define ksh_isalnux(c) ctype((c), C_ALPHA | C_DIGIT) -EXTERN int ifs0 I__(' '); /* for "$*" */ +EXTERN int ifs0 E_INIT(' '); /* for "$*" */ /* Argument parsing for built-in commands and getopts command */ @@ -758,14 +804,18 @@ EXTERN int ifs0 I__(' '); /* for "$*" */ #define GI_PLUS BIT(1) /* an option started with +... */ #define GI_MINUSMINUS BIT(2) /* arguments were ended with -- */ +/* in case some OS defines these */ +#undef optarg +#undef optind + typedef struct { - const char *optarg; - int optind; - int uoptind;/* what user sees in $OPTIND */ - int flags; /* see GF_* */ - int info; /* see GI_* */ - unsigned int p; /* 0 or index into argv[optind - 1] */ - char buf[2]; /* for bad option OPTARG value */ + const char *optarg; + int optind; + int uoptind; /* what user sees in $OPTIND */ + int flags; /* see GF_* */ + int info; /* see GI_* */ + unsigned int p; /* 0 or index into argv[optind - 1] */ + char buf[2]; /* for bad option OPTARG value */ } Getopt; EXTERN Getopt builtin_opt; /* for shell builtin commands */ @@ -773,7 +823,9 @@ EXTERN Getopt user_opt; /* parsing state for getopts builtin command */ /* This for co-processes */ -typedef int32_t Coproc_id; /* something that won't (realisticly) wrap */ +/* something that won't (realisticly) wrap */ +typedef int32_t Coproc_id; + struct coproc { void *job; /* 0 or job of co-process using input pipe */ int read; /* pipe from co-process's stdout */ @@ -784,27 +836,31 @@ struct coproc { }; EXTERN struct coproc coproc; -/* Used in jobs.c and by coprocess stuff in exec.c */ +#ifndef MKSH_NOPROSPECTOFWORK +/* used in jobs.c and by coprocess stuff in exec.c and select() calls */ EXTERN sigset_t sm_default, sm_sigchld; +#endif /* name of called builtin function (used by error functions) */ EXTERN const char *builtin_argv0; -EXTERN Tflag builtin_flag; /* flags of called builtin (SPEC_BI, etc.) */ +/* flags of called builtin (SPEC_BI, etc.) */ +EXTERN uint32_t builtin_flag; -/* current working directory, and size of memory allocated for same */ +/* current working directory */ EXTERN char *current_wd; -EXTERN size_t current_wd_size; -/* Minimum required space to work with on a line - if the prompt leaves less - * space than this on a line, the prompt is truncated. +/* + * Minimum required space to work with on a line - if the prompt leaves + * less space than this on a line, the prompt is truncated. */ #define MIN_EDIT_SPACE 7 -/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line +/* + * Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line */ #define MIN_COLS (2 + MIN_EDIT_SPACE + 3) #define MIN_LINS 3 -EXTERN mksh_ari_t x_cols I__(80); /* tty columns */ -EXTERN mksh_ari_t x_lins I__(-1); /* tty lines */ +EXTERN mksh_ari_t x_cols E_INIT(80); /* tty columns */ +EXTERN mksh_ari_t x_lins E_INIT(-1); /* tty lines */ /* These to avoid bracket matching problems */ #define OPAREN '(' @@ -814,8 +870,19 @@ EXTERN mksh_ari_t x_lins I__(-1); /* tty lines */ #define OBRACE '{' #define CBRACE '}' + /* Determine the location of the system (common) profile */ -#define KSH_SYSTEM_PROFILE "/etc/profile" + +/* This is deliberately not configurable via CPPFLAGS */ +#if defined(ANDROID) +#define MKSH_ETC_LOCATION "/system/etc" +#else +#define MKSH_ETC_LOCATION "/etc" +#endif + +#define MKSH_SYSTEM_PROFILE MKSH_ETC_LOCATION "/profile" +#define MKSH_SUID_PROFILE MKSH_ETC_LOCATION "/suid_profile" + /* Used by v_evaluate() and setstr() to control action when error occurs */ #define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */ @@ -825,24 +892,19 @@ EXTERN mksh_ari_t x_lins I__(-1); /* tty lines */ * Shell file I/O routines */ -#define SHF_BSIZE 512 +#define SHF_BSIZE 512 -#define shf_fileno(shf) ((shf)->fd) +#define shf_fileno(shf) ((shf)->fd) #define shf_setfileno(shf,nfd) ((shf)->fd = (nfd)) -#ifdef MKSH_SMALL -int shf_getc(struct shf *); -int shf_putc(int, struct shf *); -#else -#define shf_getc(shf) ((shf)->rnleft > 0 ? \ +#define shf_getc_(shf) ((shf)->rnleft > 0 ? \ (shf)->rnleft--, *(shf)->rp++ : \ shf_getchar(shf)) -#define shf_putc(c, shf) ((shf)->wnleft == 0 ? \ +#define shf_putc_(c, shf) ((shf)->wnleft == 0 ? \ shf_putchar((c), (shf)) : \ ((shf)->wnleft--, *(shf)->wp++ = (c))) -#endif #define shf_eof(shf) ((shf)->flags & SHF_EOF) #define shf_error(shf) ((shf)->flags & SHF_ERROR) -#define shf_errno(shf) ((shf)->errno_) +#define shf_errno(shf) ((shf)->errnosv) #define shf_clearerr(shf) ((shf)->flags &= ~(SHF_EOF | SHF_ERROR)) /* Flags passed to shf_*open() */ @@ -872,14 +934,14 @@ struct shf { unsigned char *rp; /* read: current position in buffer */ unsigned char *wp; /* write: current position in buffer */ unsigned char *buf; /* buffer */ + ssize_t bsize; /* actual size of buf */ + ssize_t rbsize; /* size of buffer (1 if SHF_UNBUF) */ + ssize_t rnleft; /* read: how much data left in buffer */ + ssize_t wbsize; /* size of buffer (0 if SHF_UNBUF) */ + ssize_t wnleft; /* write: how much space left in buffer */ int flags; /* see SHF_* */ - int rbsize; /* size of buffer (1 if SHF_UNBUF) */ - int rnleft; /* read: how much data left in buffer */ - int wbsize; /* size of buffer (0 if SHF_UNBUF) */ - int wnleft; /* write: how much space left in buffer */ int fd; /* file descriptor */ - int errno_; /* saved value of errno after error */ - int bsize; /* actual size of buf */ + int errnosv; /* saved value of errno after error */ }; extern struct shf shf_iob[]; @@ -887,34 +949,44 @@ extern struct shf shf_iob[]; struct table { Area *areap; /* area to allocate entries */ struct tbl **tbls; /* hashed table items */ - short size, nfree; /* hash size (always 2^^n), free entries */ + size_t nfree; /* free table entries */ + uint8_t tshift; /* table size (2^tshift) */ }; -struct tbl { /* table item */ - Area *areap; /* area to allocate from */ +/* table item */ +struct tbl { + /* Area to allocate from */ + Area *areap; + /* value */ union { - char *s; /* string */ - mksh_ari_t i; /* integer */ - mksh_uari_t u; /* unsigned integer */ - int (*f)(const char **);/* int function */ - struct op *t; /* "function" tree */ - } val; /* value */ + char *s; /* string */ + mksh_ari_t i; /* integer */ + mksh_uari_t u; /* unsigned integer */ + int (*f)(const char **); /* built-in command */ + struct op *t; /* "function" tree */ + } val; union { struct tbl *array; /* array values */ const char *fpath; /* temporary path to undef function */ } u; union { - int field; /* field with for -L/-R/-Z */ - int errno_; /* CEXEC/CTALIAS */ + int field; /* field with for -L/-R/-Z */ + int errnov; /* CEXEC/CTALIAS */ } u2; - int type; /* command type (see below), base (if INTEGER), - * or offset from val.s of value (if EXPORT) */ - Tflag flag; /* flags */ union { uint32_t hval; /* hash(name) */ uint32_t index; /* index for an array */ } ua; - char name[4]; /* name -- variable length */ + /* + * command type (see below), base (if INTEGER), + * offset from val.s of value (if EXPORT) + */ + int type; + /* flags (see below) */ + uint32_t flag; + + /* actually longer: name (variable length) */ + char name[4]; }; /* common flag bits */ @@ -936,7 +1008,7 @@ struct tbl { /* table item */ #define LCASEV BIT(17) /* convert to lower case */ #define UCASEV_AL BIT(18) /* convert to upper case / autoload function */ #define INT_U BIT(19) /* unsigned integer */ -#define INT_L BIT(20) /* long integer (no-op) */ +#define INT_L BIT(20) /* long integer (no-op but used as magic) */ #define IMPORT BIT(21) /* flag to typeset(): no arrays, must have = */ #define LOCAL_COPY BIT(22) /* with LOCAL - copy attrs from existing var */ #define EXPRINEVAL BIT(23) /* contents currently being evaluated */ @@ -950,8 +1022,10 @@ struct tbl { /* table item */ #define FKSH BIT(11) /* function defined with function x (vs x()) */ #define SPEC_BI BIT(12) /* a POSIX special builtin */ #define REG_BI BIT(13) /* a POSIX regular builtin */ -/* Attributes that can be set by the user (used to decide if an unset param - * should be repoted by set/typeset). Does not include ARRAY or LOCAL. +/* + * Attributes that can be set by the user (used to decide if an unset + * param should be repoted by set/typeset). Does not include ARRAY or + * LOCAL. */ #define USERATTRIB (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\ LCASEV|UCASEV_AL|INT_U|INT_L) @@ -959,6 +1033,12 @@ struct tbl { /* table item */ #define arrayindex(vp) ((unsigned long)((vp)->flag & AINDEX ? \ (vp)->ua.index : 0)) +EXTERN enum { + SRF_NOP, + SRF_ENABLE, + SRF_DISABLE +} set_refflag E_INIT(SRF_NOP); + /* command types */ #define CNONE 0 /* undefined */ #define CSHELL 1 /* built-in */ @@ -981,13 +1061,13 @@ struct tbl { /* table item */ #define AF_ARGV_ALLOC 0x1 /* argv[] array allocated */ #define AF_ARGS_ALLOCED 0x2 /* argument strings allocated */ #define AI_ARGV(a, i) ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip]) -#define AI_ARGC(a) ((a).argc_ - (a).skip) +#define AI_ARGC(a) ((a).ai_argc - (a).skip) /* Argument info. Used for $#, $* for shell, functions, includes, etc. */ struct arg_info { const char **argv; int flags; /* AF_* */ - int argc_; + int ai_argc; int skip; /* first arg is argv[0], second is argv[1 + skip] */ }; @@ -1063,9 +1143,14 @@ struct op { */ int lineno; /* TCOM/TFUNC: LINENO for this */ short type; /* operation type, see below */ - union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ - short evalflags; /* TCOM: arg expansion eval() flags */ - short ksh_func; /* TFUNC: function x (vs x()) */ + /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ + union { + /* TCOM: arg expansion eval() flags */ + short evalflags; + /* TFUNC: function x (vs x()) */ + short ksh_func; + /* TPAT: termination character */ + char charflag; } u; }; @@ -1115,27 +1200,29 @@ struct op { * IO redirection */ struct ioword { - int unit; /* unit affected */ - int flag; /* action (below) */ - char *name; /* file name (unused if heredoc) */ - char *delim; /* delimiter for <<,<<- */ - char *heredoc;/* content of heredoc */ + int unit; /* unit affected */ + int flag; /* action (below) */ + char *name; /* file name (unused if heredoc) */ + char *delim; /* delimiter for <<,<<- */ + char *heredoc; /* content of heredoc */ }; /* ioword.flag - type of redirection */ -#define IOTYPE 0xF /* type: bits 0:3 */ -#define IOREAD 0x1 /* < */ -#define IOWRITE 0x2 /* > */ -#define IORDWR 0x3 /* <>: todo */ -#define IOHERE 0x4 /* << (here file) */ -#define IOCAT 0x5 /* >> */ -#define IODUP 0x6 /* <&/>& */ -#define IOEVAL BIT(4) /* expand in << */ -#define IOSKIP BIT(5) /* <<-, skip ^\t* */ -#define IOCLOB BIT(6) /* >|, override -o noclobber */ -#define IORDUP BIT(7) /* x<&y (as opposed to x>&y) */ -#define IONAMEXP BIT(8) /* name has been expanded */ -#define IOBASH BIT(9) /* &> etc. */ +#define IOTYPE 0xF /* type: bits 0:3 */ +#define IOREAD 0x1 /* < */ +#define IOWRITE 0x2 /* > */ +#define IORDWR 0x3 /* <>: todo */ +#define IOHERE 0x4 /* << (here file) */ +#define IOCAT 0x5 /* >> */ +#define IODUP 0x6 /* <&/>& */ +#define IOEVAL BIT(4) /* expand in << */ +#define IOSKIP BIT(5) /* <<-, skip ^\t* */ +#define IOCLOB BIT(6) /* >|, override -o noclobber */ +#define IORDUP BIT(7) /* x<&y (as opposed to x>&y) */ +#define IONAMEXP BIT(8) /* name has been expanded */ +#define IOBASH BIT(9) /* &> etc. */ +#define IOHERESTR BIT(10) /* <<< (here string) */ +#define IONDELIM BIT(11) /* null delimiter (<<) */ /* execute/exchild flags */ #define XEXEC BIT(0) /* execute without forking */ @@ -1150,6 +1237,7 @@ struct ioword { #define XERROK BIT(8) /* non-zero exit ok (for set -e) */ #define XCOPROC BIT(9) /* starting a co-process */ #define XTIME BIT(10) /* timing TCOM command */ +#define XPIPEST BIT(11) /* want PIPESTATUS */ /* * flags to control expansion of words (assumed by t->evalflags to fit @@ -1161,9 +1249,9 @@ struct ioword { #define DOTILDE BIT(3) /* normal ~ expansion (first char) */ #define DONTRUNCOMMAND BIT(4) /* do not run $(command) things */ #define DOASNTILDE BIT(5) /* assignment ~ expansion (after =, :) */ -#define DOBRACE_ BIT(6) /* used by expand(): do brace expansion */ -#define DOMAGIC_ BIT(7) /* used by expand(): string contains MAGIC */ -#define DOTEMP_ BIT(8) /* ditto : in word part of ${..[%#=?]..} */ +#define DOBRACE BIT(6) /* used by expand(): do brace expansion */ +#define DOMAGIC BIT(7) /* used by expand(): string contains MAGIC */ +#define DOTEMP BIT(8) /* dito: in word part of ${..[%#=?]..} */ #define DOVACHECK BIT(9) /* var assign check (for typeset, set, etc) */ #define DOMARKDIRS BIT(10) /* force markdirs behaviour */ @@ -1180,7 +1268,7 @@ struct ioword { #define DB_BE 4 /* an inserted -BE */ #define DB_PAT 5 /* a pattern argument */ -#define X_EXTRA 8 /* this many extra bytes in X string */ +#define X_EXTRA 20 /* this many extra bytes in X string */ typedef struct XString { char *end, *beg; /* end, begin of string */ @@ -1207,7 +1295,7 @@ typedef char *XStringP; /* check if there are at least n bytes left */ #define XcheckN(xs, xp, n) do { \ - int more = ((xp) + (n)) - (xs).end; \ + ssize_t more = ((xp) + (n)) - (xs).end; \ if (more > 0) \ (xp) = Xcheck_grow_(&(xs), (xp), more); \ } while (/* CONSTCOND */ 0) @@ -1230,7 +1318,7 @@ typedef char *XStringP; #define Xsavepos(xs, xp) ((xp) - (xs).beg) #define Xrestpos(xs, xp, n) ((xs).beg + (n)) -char *Xcheck_grow_(XString *, const char *, unsigned int); +char *Xcheck_grow_(XString *, const char *, size_t); /* * expandable vector of generic pointers @@ -1242,17 +1330,17 @@ typedef struct XPtrV { } XPtrV; #define XPinit(x, n) do { \ - void **vp__; \ - vp__ = alloc((n) * sizeof(void *), ATEMP); \ - (x).cur = (x).beg = vp__; \ - (x).end = vp__ + (n); \ + void **XPinit_vp; \ + XPinit_vp = alloc2((n), sizeof(void *), ATEMP); \ + (x).cur = (x).beg = XPinit_vp; \ + (x).end = XPinit_vp + (n); \ } while (/* CONSTCOND */ 0) #define XPput(x, p) do { \ if ((x).cur >= (x).end) { \ size_t n = XPsize(x); \ - (x).beg = aresize((x).beg, \ - n * 2 * sizeof(void *), ATEMP); \ + (x).beg = aresize2((x).beg, \ + n, 2 * sizeof(void *), ATEMP); \ (x).cur = (x).beg + n; \ (x).end = (x).cur + n; \ } \ @@ -1261,7 +1349,7 @@ typedef struct XPtrV { #define XPptrv(x) ((x).beg) #define XPsize(x) ((x).cur - (x).beg) -#define XPclose(x) aresize((x).beg, XPsize(x) * sizeof(void *), ATEMP) +#define XPclose(x) aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP) #define XPfree(x) afree((x).beg, ATEMP) #define IDENT 64 @@ -1304,8 +1392,7 @@ struct source { #define SF_ALIAS BIT(1) /* faking space at end of alias */ #define SF_ALIASEND BIT(2) /* faking space at end of alias */ #define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */ -#define SF_FIRST BIT(4) /* initial state (to ignore UTF-8 BOM) */ -#define SF_HASALIAS BIT(5) /* u.tblp valid (SALIAS, SEOF) */ +#define SF_HASALIAS BIT(4) /* u.tblp valid (SALIAS, SEOF) */ typedef union { int i; @@ -1341,6 +1428,8 @@ typedef union { #define BANG 278 /* ! */ #define DBRACKET 279 /* [[ .. ]] */ #define COPROC 280 /* |& */ +#define BRKEV 281 /* ;| */ +#define BRKFT 282 /* ;& */ #define YYERRCODE 300 /* flags to yylex */ @@ -1356,18 +1445,17 @@ typedef union { #define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */ #define LQCHAR BIT(10) /* source string contains QCHAR */ #define HEREDOC BIT(11) /* parsing a here document */ -#define LETARRAY BIT(12) /* copy expression inside =( ) */ -#define HERES 10 /* max << in line */ +#define HERES 10 /* max number of << in line */ #undef CTRL #define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ #define UNCTRL(x) ((x) ^ 0x40) /* ASCII */ EXTERN Source *source; /* yyparse/yylex source */ -EXTERN YYSTYPE yylval; /* result from yylex */ -EXTERN struct ioword *heres [HERES], **herep; -EXTERN char ident [IDENT+1]; +EXTERN YYSTYPE yylval; /* result from yylex */ +EXTERN struct ioword *heres[HERES], **herep; +EXTERN char ident[IDENT+1]; #define HISTORYSIZE 500 /* size of saved history */ @@ -1378,12 +1466,75 @@ EXTERN int histsize; /* history size */ /* user and system time of last j_waitjed job */ EXTERN struct timeval j_usrtime, j_systime; +#define notoktomul(fac1, fac2) (((fac1) != 0) && ((fac2) != 0) && \ + ((SIZE_MAX / (fac1)) < (fac2))) +#define notoktoadd(val, cnst) ((val) > (SIZE_MAX - (cnst))) +#define checkoktoadd(val, cnst) do { \ + if (notoktoadd((val), (cnst))) \ + internal_errorf(Tintovfl, (size_t)(val), \ + '+', (size_t)(cnst)); \ +} while (/* CONSTCOND */ 0) + + +/* NZAT/NZAAT hashes based on Bob Jenkins' one-at-a-time hash */ + +/* From: src/kern/include/nzat.h,v 1.2 2011/07/18 00:35:40 tg Exp $ */ + +#define NZATInit(h) do { \ + (h) = 0; \ +} while (/* CONSTCOND */ 0) + +#define NZATUpdateByte(h,b) do { \ + (h) += (uint8_t)(b); \ + ++(h); \ + (h) += (h) << 10; \ + (h) ^= (h) >> 6; \ +} while (/* CONSTCOND */ 0) + +#define NZATUpdateMem(h,p,z) do { \ + register const uint8_t *NZATUpdateMem_p; \ + register size_t NZATUpdateMem_z = (z); \ + \ + NZATUpdateMem_p = (const void *)(p); \ + while (NZATUpdateMem_z--) \ + NZATUpdateByte((h), *NZATUpdateMem_p++); \ +} while (/* CONSTCOND */ 0) + +#define NZATUpdateString(h,s) do { \ + register const char *NZATUpdateString_s; \ + register uint8_t NZATUpdateString_c; \ + \ + NZATUpdateString_s = (const void *)(s); \ + while ((NZATUpdateString_c = *NZATUpdateString_s++)) \ + NZATUpdateByte((h), NZATUpdateString_c); \ +} while (/* CONSTCOND */ 0) + +/* not zero after termination */ +#define NZATFinish(h) do { \ + if ((h) == 0) \ + ++(h); \ + else \ + NZAATFinish(h); \ +} while (/* CONSTCOND */ 0) + +/* NULs zählen an allen Teilen */ +#define NZAATFinish(h) do { \ + (h) += (h) << 10; \ + (h) ^= (h) >> 6; \ + (h) += (h) << 3; \ + (h) ^= (h) >> 11; \ + (h) += (h) << 15; \ +} while (/* CONSTCOND */ 0) + + /* lalloc.c */ void ainit(Area *); void afreeall(Area *); /* these cannot fail and can take NULL (not for ap) */ -#define alloc(n, ap) aresize(NULL, (n), (ap)) +#define alloc(n, ap) aresize(NULL, (n), (ap)) +#define alloc2(m, n, ap) aresize2(NULL, (m), (n), (ap)) void *aresize(void *, size_t, Area *); +void *aresize2(void *, size_t, size_t, Area *); void afree(void *, Area *); /* can take NULL */ /* edit.c */ #ifndef MKSH_SMALL @@ -1392,6 +1543,7 @@ int x_bind(const char *, const char *, bool, bool); int x_bind(const char *, const char *, bool); #endif void x_init(void); +void x_mkraw(int, struct termios *, bool); int x_read(char *, size_t); /* eval.c */ char *substitute(const char *, int); @@ -1406,11 +1558,10 @@ int execute(struct op * volatile, volatile int, volatile int * volatile); int shcomexec(const char **); struct tbl *findfunc(const char *, uint32_t, bool); int define(const char *, struct op *); -void builtin(const char *, int (*)(const char **)); +const char *builtin(const char *, int (*)(const char **)); struct tbl *findcom(const char *, int); -void flushcom(int); -const char *search(const char *, const char *, int, int *); -int search_access(const char *, int, int *); +void flushcom(bool); +const char *search_path(const char *, const char *, int, int *); int pr_menu(const char * const *); int pr_list(char * const *); /* expr.c */ @@ -1420,15 +1571,15 @@ int v_evaluate(struct tbl *, const char *, volatile int, bool); size_t utf_mbtowc(unsigned int *, const char *); size_t utf_wctomb(char *, unsigned int); int utf_widthadj(const char *, const char **); -int utf_mbswidth(const char *); +size_t utf_mbswidth(const char *); const char *utf_skipcols(const char *, int); size_t utf_ptradj(const char *); #ifndef MKSH_mirbsd_wcwidth int utf_wcwidth(unsigned int); #endif +int ksh_access(const char *, int); /* funcs.c */ int c_hash(const char **); -int c_cd(const char **); int c_pwd(const char **); int c_print(const char **); #ifdef MKSH_PRINTF_BUILTIN @@ -1448,7 +1599,6 @@ int c_kill(const char **); void getopts_reset(int); int c_getopts(const char **); int c_bind(const char **); -int c_label(const char **); int c_shift(const char **); int c_umask(const char **); int c_dot(const char **); @@ -1465,13 +1615,16 @@ int c_times(const char **); int timex(struct op *, int, volatile int *); void timex_hook(struct op *, char ** volatile *); int c_exec(const char **); -int c_builtin(const char **); +/* dummy function (just need pointer value), special case in comexec() */ +#define c_builtin shcomexec int c_test(const char **); #if HAVE_MKNOD int c_mknod(const char **); #endif int c_realpath(const char **); int c_rename(const char **); +int c_cat(const char **); +int c_sleep(const char **); /* histrap.c */ void init_histvec(void); void hist_init(Source *); @@ -1490,7 +1643,6 @@ void sethistfile(const char *); char **histpos(void); int histnum(int); int findhist(int, int, const char *, int); -int findhistrel(const char *); char **hist_get_newest(bool); void inittraps(void); void alarm_init(void); @@ -1500,7 +1652,7 @@ void intrcheck(void); int fatal_trap_check(void); int trap_pending(void); void runtraps(int intr); -void runtrap(Trap *); +void runtrap(Trap *, bool); void cleartraps(void); void restoresigs(void); void settrap(Trap *, const char *); @@ -1523,7 +1675,6 @@ int j_kill(const char *, int); int j_resume(const char *, int); #endif int j_jobs(const char *, int, int); -int j_njobs(void); void j_notify(void); pid_t j_async(void); int j_stopped_running(void); @@ -1531,7 +1682,7 @@ int j_stopped_running(void); int yylex(int); void yyerror(const char *, ...) MKSH_A_NORETURN - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); Source *pushs(int, Area *); void set_prompt(int, Source *); void pprompt(const char *, int); @@ -1547,25 +1698,27 @@ void cleanup_parents_env(void); void cleanup_proc_env(void); void errorf(const char *, ...) MKSH_A_NORETURN - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); +void errorfx(int, const char *, ...) + MKSH_A_NORETURN + MKSH_A_FORMAT(__printf__, 2, 3); void warningf(bool, const char *, ...) - MKSH_A_FORMAT(printf, 2, 3); + MKSH_A_FORMAT(__printf__, 2, 3); void bi_errorf(const char *, ...) - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); #define errorfz() errorf("\1") +#define errorfxz(rc) errorfx((rc), "\1") #define bi_errorfz() bi_errorf("\1") -void internal_verrorf(const char *, va_list) - MKSH_A_FORMAT(printf, 1, 0); void internal_errorf(const char *, ...) MKSH_A_NORETURN - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); void internal_warningf(const char *, ...) - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); void error_prefix(bool); void shellf(const char *, ...) - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); void shprintf(const char *, ...) - MKSH_A_FORMAT(printf, 1, 2); + MKSH_A_FORMAT(__printf__, 1, 2); int can_seek(int); void initio(void); int ksh_dup2(int, int, bool); @@ -1581,11 +1734,10 @@ void coproc_write_close(int); int coproc_getfd(int, const char **); void coproc_cleanup(int); struct temp *maketemp(Area *, Temp_type, struct temp **); -#define hash(s) oaathash_full((const uint8_t *)(s)) -uint32_t oaathash_full(register const uint8_t *); -uint32_t hashmem(const void *, size_t); -void ktinit(struct table *, Area *, size_t); -struct tbl *ktsearch(struct table *, const char *, uint32_t); +void ktinit(Area *, struct table *, uint8_t); +struct tbl *ktscan(struct table *, const char *, uint32_t, struct tbl ***); +/* table, name (key) to search for, hash(n) */ +#define ktsearch(tp, s, h) ktscan((tp), (s), (h), NULL) struct tbl *ktenter(struct table *, const char *, uint32_t); #define ktdelete(p) do { p->flag = 0; } while (/* CONSTCOND */ 0) void ktwalk(struct tstate *, struct table *); @@ -1602,22 +1754,23 @@ int getn(const char *, int *); int bi_getn(const char *, int *); int gmatchx(const char *, const char *, bool); int has_globbing(const char *, const char *); -const unsigned char *pat_scan(const unsigned char *, const unsigned char *, int); int xstrcmp(const void *, const void *); void ksh_getopt_reset(Getopt *, int); int ksh_getopt(const char **, Getopt *, const char *); void print_value_quoted(const char *); +char *quote_value(const char *); void print_columns(struct shf *, int, - char *(*)(char *, int, int, const void *), - const void *, int, int, bool); + char *(*)(char *, size_t, int, const void *), + const void *, size_t, size_t, bool); void strip_nuls(char *, int); -int blocking_read(int, char *, int) - MKSH_A_BOUNDED(buffer, 2, 3); +ssize_t blocking_read(int, char *, size_t) + MKSH_A_BOUNDED(__buffer__, 2, 3); int reset_nonblock(int); -char *ksh_get_wd(size_t *); -int make_path(const char *, const char *, char **, XString *, int *); +char *ksh_get_wd(void); +char *do_realpath(const char *); void simplify_path(char *); -void set_current_wd(char *); +void set_current_wd(const char *); +int c_cd(const char **); #ifdef MKSH_SMALL char *strdup_(const char *, Area *); char *strndup_(const char *, size_t, Area *); @@ -1627,38 +1780,56 @@ int unbksl(bool, int (*)(void), void (*)(int)); struct shf *shf_open(const char *, int, int, int); struct shf *shf_fdopen(int, int, struct shf *); struct shf *shf_reopen(int, int, struct shf *); -struct shf *shf_sopen(char *, int, int, struct shf *); +struct shf *shf_sopen(char *, ssize_t, int, struct shf *); int shf_close(struct shf *); int shf_fdclose(struct shf *); char *shf_sclose(struct shf *); int shf_flush(struct shf *); -int shf_read(char *, int, struct shf *); -char *shf_getse(char *, int, struct shf *); +ssize_t shf_read(char *, ssize_t, struct shf *); +char *shf_getse(char *, ssize_t, struct shf *); int shf_getchar(struct shf *s); int shf_ungetc(int, struct shf *); +#ifdef MKSH_SMALL +int shf_getc(struct shf *); +int shf_putc(int, struct shf *); +#else +#define shf_getc shf_getc_ +#define shf_putc shf_putc_ +#endif int shf_putchar(int, struct shf *); -int shf_puts(const char *, struct shf *); -int shf_write(const char *, int, struct shf *); -int shf_fprintf(struct shf *, const char *, ...) - MKSH_A_FORMAT(printf, 2, 3); -int shf_snprintf(char *, int, const char *, ...) - MKSH_A_FORMAT(printf, 3, 4) - MKSH_A_BOUNDED(string, 1, 2); +ssize_t shf_puts(const char *, struct shf *); +ssize_t shf_write(const char *, ssize_t, struct shf *); +ssize_t shf_fprintf(struct shf *, const char *, ...) + MKSH_A_FORMAT(__printf__, 2, 3); +ssize_t shf_snprintf(char *, ssize_t, const char *, ...) + MKSH_A_FORMAT(__printf__, 3, 4) + MKSH_A_BOUNDED(__string__, 1, 2); char *shf_smprintf(const char *, ...) - MKSH_A_FORMAT(printf, 1, 2); -int shf_vfprintf(struct shf *, const char *, va_list) - MKSH_A_FORMAT(printf, 2, 0); + MKSH_A_FORMAT(__printf__, 1, 2); +ssize_t shf_vfprintf(struct shf *, const char *, va_list) + MKSH_A_FORMAT(__printf__, 2, 0); /* syn.c */ void initkeywords(void); -struct op *compile(Source *); +struct op *compile(Source *, bool); +bool parse_usec(const char *, struct timeval *); +char *yyrecursive(void); /* tree.c */ -int fptreef(struct shf *, int, const char *, ...); -char *snptreef(char *, int, const char *, ...); +void fptreef(struct shf *, int, const char *, ...); +char *snptreef(char *, ssize_t, const char *, ...); struct op *tcopy(struct op *, Area *); char *wdcopy(const char *, Area *); const char *wdscan(const char *, int); -char *wdstrip(const char *, bool, bool); +#define WDS_TPUTS BIT(0) /* tputS (dumpwdvar) mode */ +#define WDS_KEEPQ BIT(1) /* keep quote characters */ +#define WDS_MAGIC BIT(2) /* make MAGIC */ +char *wdstrip(const char *, int); void tfree(struct op *, Area *); +void dumpchar(struct shf *, int); +void dumptree(struct shf *, struct op *); +void dumpwdvar(struct shf *, const char *); +void vistree(char *, size_t, struct op *) + MKSH_A_BOUNDED(__string__, 1, 2); +void fpFUNCTf(struct shf *, int, bool, const char *, struct op *); /* var.c */ void newblock(void); void popblock(void); @@ -1669,22 +1840,26 @@ char *str_val(struct tbl *); int setstr(struct tbl *, const char *, int); struct tbl *setint_v(struct tbl *, struct tbl *, bool); void setint(struct tbl *, mksh_ari_t); -struct tbl *typeset(const char *, Tflag, Tflag, int, int) - MKSH_A_NONNULL((nonnull (1))); +void setint_n(struct tbl *, mksh_ari_t); +struct tbl *typeset(const char *, uint32_t, uint32_t, int, int) + MKSH_A_NONNULL((__nonnull__ (1))); void unset(struct tbl *, int); const char *skip_varname(const char *, int); -const char *skip_wdvarname(const char *, int); -int is_wdvarname(const char *, int); +const char *skip_wdvarname(const char *, bool); +int is_wdvarname(const char *, bool); int is_wdvarassign(const char *); +struct tbl *arraysearch(struct tbl *, uint32_t); char **makenv(void); -void change_random(const void *, size_t); void change_winsz(void); -int array_ref_len(const char *); +size_t array_ref_len(const char *); char *arrayname(const char *); mksh_uari_t set_array(const char *, bool, const char **); +uint32_t hash(const void *); +void rndset(long); enum Test_op { - TO_NONOP = 0, /* non-operator */ + /* non-operator */ + TO_NONOP = 0, /* unary operators */ TO_STNZE, TO_STZER, TO_OPTION, TO_FILAXST, @@ -1718,15 +1893,15 @@ typedef enum Test_meta Test_meta; typedef struct test_env { union { - const char **wp;/* used by ptest_* */ - XPtrV *av; /* used by dbtestp_* */ + const char **wp; /* used by ptest_* */ + XPtrV *av; /* used by dbtestp_* */ } pos; - const char **wp_end; /* used by ptest_* */ + const char **wp_end; /* used by ptest_* */ Test_op (*isa)(struct test_env *, Test_meta); const char *(*getopnd) (struct test_env *, Test_op, bool); int (*eval)(struct test_env *, Test_op, const char *, const char *, bool); void (*error)(struct test_env *, int, const char *); - int flags; /* TEF_* */ + int flags; /* TEF_* */ } Test_env; extern const char *const dbtest_tokens[]; @@ -1735,8 +1910,8 @@ Test_op test_isop(Test_meta, const char *); int test_eval(Test_env *, Test_op, const char *, const char *, bool); int test_parse(Test_env *); -EXTERN int tty_fd I__(-1); /* dup'd tty file descriptor */ -EXTERN int tty_devtty; /* true if tty_fd is from /dev/tty */ +EXTERN int tty_fd E_INIT(-1); /* dup'd tty file descriptor */ +EXTERN bool tty_devtty; /* true if tty_fd is from /dev/tty */ EXTERN struct termios tty_state; /* saved tty state */ extern void tty_init(bool, bool); @@ -1747,6 +1922,6 @@ extern void tty_close(void); # undef EXTERN_DEFINED # undef EXTERN #endif -#undef I__ +#undef E_INIT #endif /* !MKSH_INCLUDES_ONLY */ diff --git a/src/sh_flags.h b/src/sh_flags.h index aa5481e..a850220 100644 --- a/src/sh_flags.h +++ b/src/sh_flags.h @@ -1,5 +1,5 @@ #if defined(SHFLAGS_DEFNS) -__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.9 2011/06/12 15:37:10 tg Exp $"); #define FN(sname,cname,ochar,flags) /* nothing */ #elif defined(SHFLAGS_ENUMS) #define FN(sname,cname,ochar,flags) cname, @@ -21,9 +21,6 @@ __RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $"); /* -a all new parameters are created with the export attribute */ F0("allexport", FEXPORT, 'a', OF_ANY) -/* ./. backwards compat: dummy, emits a warning */ -FN("arc4random", FARC4RANDOM, 0, OF_ANY) - #if HAVE_NICE /* ./. bgnice */ FN("bgnice", FBGNICE, 0, OF_ANY) @@ -135,6 +132,9 @@ FN(NULL, FCOMMAND, 'c', OF_CMDLINE) * anonymous flags: used internally by shell only (not visible to user) */ +/* ./. direct builtin call (divined from argv[0] multi-call binary) */ +FN(NULL, FAS_BUILTIN, 0, OF_INTERNAL) + /* ./. (internal) initial shell was interactive */ FN(NULL, FTALKING_I, 0, OF_INTERNAL) @@ -1,7 +1,7 @@ /* $OpenBSD: shf.c,v 1.15 2006/04/02 00:48:33 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -18,11 +18,13 @@ * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out * of said person's immediate fault when using the work as intended. + *- + * Use %zX instead of %p and floating point isn't supported at all. */ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.44 2011/09/07 15:24:20 tg Exp $"); /* flags to shf_emptybuf() */ #define EB_READSW 0x01 /* about to switch to reading */ @@ -37,7 +39,8 @@ __RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $"); static int shf_fillbuf(struct shf *); static int shf_emptybuf(struct shf *, int); -/* Open a file. First three args are for open(), last arg is flags for +/* + * Open a file. First three args are for open(), last arg is flags for * this package. Returns NULL if file could not be opened, or if a dup * fails. */ @@ -45,7 +48,9 @@ struct shf * shf_open(const char *name, int oflags, int mode, int sflags) { struct shf *shf; - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + ssize_t bsize = + /* at most 512 */ + sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; int fd; /* Done before open so if alloca fails, fd won't be lost. */ @@ -79,11 +84,11 @@ shf_open(const char *name, int oflags, int mode, int sflags) return (shf_reopen(fd, sflags, shf)); } -/* Set up the shf structure for a file descriptor. Doesn't fail. */ -struct shf * -shf_fdopen(int fd, int sflags, struct shf *shf) +/* helper function for shf_fdopen and shf_reopen */ +static void +shf_open_hlp(int fd, int *sflagsp, const char *where) { - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + int sflags = *sflagsp; /* use fcntl() to figure out correct read/write flags */ if (sflags & SHF_GETFL) { @@ -105,11 +110,22 @@ shf_fdopen(int fd, int sflags, struct shf *shf) break; } } + *sflagsp = sflags; } if (!(sflags & (SHF_RD | SHF_WR))) - internal_errorf("shf_fdopen: missing read/write"); + internal_errorf("%s: %s", where, "missing read/write"); +} + +/* Set up the shf structure for a file descriptor. Doesn't fail. */ +struct shf * +shf_fdopen(int fd, int sflags, struct shf *shf) +{ + ssize_t bsize = + /* at most 512 */ + sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + shf_open_hlp(fd, &sflags, "shf_fdopen"); if (shf) { if (bsize) { shf->buf = alloc(bsize, ATEMP); @@ -129,7 +145,7 @@ shf_fdopen(int fd, int sflags, struct shf *shf) shf->wnleft = 0; /* force call to shf_emptybuf() */ shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; shf->flags = sflags; - shf->errno_ = 0; + shf->errnosv = 0; shf->bsize = bsize; if (sflags & SHF_CLEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); @@ -140,34 +156,13 @@ shf_fdopen(int fd, int sflags, struct shf *shf) struct shf * shf_reopen(int fd, int sflags, struct shf *shf) { - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; - - /* use fcntl() to figure out correct read/write flags */ - if (sflags & SHF_GETFL) { - int flags = fcntl(fd, F_GETFL, 0); - - if (flags < 0) - /* will get an error on first read/write */ - sflags |= SHF_RDWR; - else { - switch (flags & O_ACCMODE) { - case O_RDONLY: - sflags |= SHF_RD; - break; - case O_WRONLY: - sflags |= SHF_WR; - break; - case O_RDWR: - sflags |= SHF_RDWR; - break; - } - } - } + ssize_t bsize = + /* at most 512 */ + sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; - if (!(sflags & (SHF_RD | SHF_WR))) - internal_errorf("shf_reopen: missing read/write"); + shf_open_hlp(fd, &sflags, "shf_reopen"); if (!shf || !shf->buf || shf->bsize < bsize) - internal_errorf("shf_reopen: bad shf/buf/bsize"); + internal_errorf("%s: %s", "shf_reopen", "bad shf/buf/bsize"); /* assumes shf->buf and shf->bsize already set up */ shf->fd = fd; @@ -177,26 +172,27 @@ shf_reopen(int fd, int sflags, struct shf *shf) shf->wnleft = 0; /* force call to shf_emptybuf() */ shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags; - shf->errno_ = 0; + shf->errnosv = 0; if (sflags & SHF_CLEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); return (shf); } -/* Open a string for reading or writing. If reading, bsize is the number +/* + * Open a string for reading or writing. If reading, bsize is the number * of bytes that can be read. If writing, bsize is the maximum number of - * bytes that can be written. If shf is not null, it is filled in and - * returned, if it is null, shf is allocated. If writing and buf is null + * bytes that can be written. If shf is not NULL, it is filled in and + * returned, if it is NULL, shf is allocated. If writing and buf is NULL * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is * used for the initial size). Doesn't fail. - * When writing, a byte is reserved for a trailing null - see shf_sclose(). + * When writing, a byte is reserved for a trailing NUL - see shf_sclose(). */ struct shf * -shf_sopen(char *buf, int bsize, int sflags, struct shf *shf) +shf_sopen(char *buf, ssize_t bsize, int sflags, struct shf *shf) { /* can't have a read+write string */ if (!(!(sflags & SHF_RD) ^ !(sflags & SHF_WR))) - internal_errorf("shf_sopen: flags 0x%x", sflags); + internal_errorf("%s: flags 0x%X", "shf_sopen", sflags); if (!shf) { shf = alloc(sizeof(struct shf), ATEMP); @@ -216,7 +212,7 @@ shf_sopen(char *buf, int bsize, int sflags, struct shf *shf) shf->wnleft = bsize - 1; /* space for a '\0' */ shf->wbsize = bsize; shf->flags = sflags | SHF_STRING; - shf->errno_ = 0; + shf->errnosv = 0; shf->bsize = bsize; return (shf); @@ -260,7 +256,8 @@ shf_fdclose(struct shf *shf) return (ret); } -/* Close a string - if it was opened for writing, it is null terminated; +/* + * Close a string - if it was opened for writing, it is NUL terminated; * returns a pointer to the string and frees shf if it was allocated * (does not free string if it was allocated). */ @@ -269,7 +266,7 @@ shf_sclose(struct shf *shf) { unsigned char *s = shf->buf; - /* null terminate */ + /* NUL terminate */ if (shf->flags & SHF_WR) { shf->wnleft++; shf_putc('\0', shf); @@ -279,7 +276,8 @@ shf_sclose(struct shf *shf) return ((char *)s); } -/* Un-read what has been read but not examined, or write what has been +/* + * Un-read what has been read but not examined, or write what has been * buffered. Returns 0 for success, EOF for (write) error. */ int @@ -289,10 +287,10 @@ shf_flush(struct shf *shf) return ((shf->flags & SHF_WR) ? EOF : 0); if (shf->fd < 0) - internal_errorf("shf_flush: no fd"); + internal_errorf("%s: %s", "shf_flush", "no fd"); if (shf->flags & SHF_ERROR) { - errno = shf->errno_; + errno = shf->errnosv; return (EOF); } @@ -310,7 +308,8 @@ shf_flush(struct shf *shf) return (0); } -/* Write out any buffered data. If currently reading, flushes the read +/* + * Write out any buffered data. If currently reading, flushes the read * buffer. Returns 0 for success, EOF for (write) error. */ static int @@ -319,15 +318,16 @@ shf_emptybuf(struct shf *shf, int flags) int ret = 0; if (!(shf->flags & SHF_STRING) && shf->fd < 0) - internal_errorf("shf_emptybuf: no fd"); + internal_errorf("%s: %s", "shf_emptybuf", "no fd"); if (shf->flags & SHF_ERROR) { - errno = shf->errno_; + errno = shf->errnosv; return (EOF); } if (shf->flags & SHF_READING) { - if (flags & EB_READSW) /* doesn't happen */ + if (flags & EB_READSW) + /* doesn't happen */ return (0); ret = shf_flush(shf); shf->flags &= ~SHF_READING; @@ -335,25 +335,26 @@ shf_emptybuf(struct shf *shf, int flags) if (shf->flags & SHF_STRING) { unsigned char *nbuf; - /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB - * is set... (changing the shf pointer could cause problems) + /* + * Note that we assume SHF_ALLOCS is not set if + * SHF_ALLOCB is set... (changing the shf pointer could + * cause problems) */ if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) || !(shf->flags & SHF_ALLOCB)) return (EOF); /* allocate more space for buffer */ - nbuf = aresize(shf->buf, 2 * shf->wbsize, shf->areap); + nbuf = aresize2(shf->buf, 2, shf->wbsize, shf->areap); shf->rp = nbuf + (shf->rp - shf->buf); shf->wp = nbuf + (shf->wp - shf->buf); shf->rbsize += shf->wbsize; shf->wnleft += shf->wbsize; - shf->wbsize *= 2; + shf->wbsize <<= 1; shf->buf = nbuf; } else { if (shf->flags & SHF_WRITING) { - int ntowrite = shf->wp - shf->buf; + ssize_t n, ntowrite = shf->wp - shf->buf; unsigned char *buf = shf->buf; - int n; while (ntowrite > 0) { n = write(shf->fd, buf, ntowrite); @@ -362,11 +363,13 @@ shf_emptybuf(struct shf *shf, int flags) !(shf->flags & SHF_INTERRUPT)) continue; shf->flags |= SHF_ERROR; - shf->errno_ = errno; + shf->errnosv = errno; shf->wnleft = 0; if (buf != shf->buf) { - /* allow a second flush - * to work */ + /* + * allow a second flush + * to work + */ memmove(shf->buf, buf, ntowrite); shf->wp = shf->buf + ntowrite; @@ -395,15 +398,17 @@ shf_emptybuf(struct shf *shf, int flags) static int shf_fillbuf(struct shf *shf) { + ssize_t n; + if (shf->flags & SHF_STRING) return (0); if (shf->fd < 0) - internal_errorf("shf_fillbuf: no fd"); + internal_errorf("%s: %s", "shf_fillbuf", "no fd"); if (shf->flags & (SHF_EOF | SHF_ERROR)) { if (shf->flags & SHF_ERROR) - errno = shf->errno_; + errno = shf->errnosv; return (EOF); } @@ -413,42 +418,39 @@ shf_fillbuf(struct shf *shf) shf->flags |= SHF_READING; shf->rp = shf->buf; - while (1) { - shf->rnleft = blocking_read(shf->fd, (char *) shf->buf, - shf->rbsize); - if (shf->rnleft < 0 && errno == EINTR && - !(shf->flags & SHF_INTERRUPT)) + while (/* CONSTCOND */ 1) { + n = blocking_read(shf->fd, (char *)shf->buf, shf->rbsize); + if (n < 0 && errno == EINTR && !(shf->flags & SHF_INTERRUPT)) continue; break; } - if (shf->rnleft <= 0) { - if (shf->rnleft < 0) { - shf->flags |= SHF_ERROR; - shf->errno_ = errno; - shf->rnleft = 0; - shf->rp = shf->buf; - return (EOF); - } - shf->flags |= SHF_EOF; + if (n < 0) { + shf->flags |= SHF_ERROR; + shf->errnosv = errno; + shf->rnleft = 0; + shf->rp = shf->buf; + return (EOF); } + if ((shf->rnleft = n) == 0) + shf->flags |= SHF_EOF; return (0); } -/* Read a buffer from shf. Returns the number of bytes read into buf, - * if no bytes were read, returns 0 if end of file was seen, EOF if - * a read error occurred. +/* + * Read a buffer from shf. Returns the number of bytes read into buf, if + * no bytes were read, returns 0 if end of file was seen, EOF if a read + * error occurred. */ -int -shf_read(char *buf, int bsize, struct shf *shf) +ssize_t +shf_read(char *buf, ssize_t bsize, struct shf *shf) { - int orig_bsize = bsize; - int ncopy; + ssize_t ncopy, orig_bsize = bsize; if (!(shf->flags & SHF_RD)) - internal_errorf("shf_read: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_read", shf->flags); if (bsize <= 0) - internal_errorf("shf_read: bsize %d", bsize); + internal_errorf("%s: %s %zd", "shf_write", "bsize", bsize); while (bsize > 0) { if (shf->rnleft == 0 && @@ -468,24 +470,27 @@ shf_read(char *buf, int bsize, struct shf *shf) orig_bsize - bsize); } -/* Read up to a newline or EOF. The newline is put in buf; buf is always - * null terminated. Returns NULL on read error or if nothing was read before - * end of file, returns a pointer to the null byte in buf otherwise. +/* + * Read up to a newline or EOF. The newline is put in buf; buf is always + * NUL terminated. Returns NULL on read error or if nothing was read + * before end of file, returns a pointer to the NUL byte in buf + * otherwise. */ char * -shf_getse(char *buf, int bsize, struct shf *shf) +shf_getse(char *buf, ssize_t bsize, struct shf *shf) { unsigned char *end; - int ncopy; + ssize_t ncopy; char *orig_buf = buf; if (!(shf->flags & SHF_RD)) - internal_errorf("shf_getse: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_getse", shf->flags); if (bsize <= 0) return (NULL); - --bsize; /* save room for null */ + /* save room for NUL */ + --bsize; do { if (shf->rnleft == 0) { if (shf_fillbuf(shf) == EOF) @@ -495,7 +500,7 @@ shf_getse(char *buf, int bsize, struct shf *shf) return (buf == orig_buf ? NULL : buf); } } - end = (unsigned char *)memchr((char *) shf->rp, '\n', + end = (unsigned char *)memchr((char *)shf->rp, '\n', shf->rnleft); ncopy = end ? end - shf->rp + 1 : shf->rnleft; if (ncopy > bsize) @@ -515,7 +520,7 @@ int shf_getchar(struct shf *shf) { if (!(shf->flags & SHF_RD)) - internal_errorf("shf_getchar: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_getchar", shf->flags); if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0)) return (EOF); @@ -523,14 +528,15 @@ shf_getchar(struct shf *shf) return (*shf->rp++); } -/* Put a character back in the input stream. Returns the character if +/* + * Put a character back in the input stream. Returns the character if * successful, EOF if there is no room. */ int shf_ungetc(int c, struct shf *shf) { if (!(shf->flags & SHF_RD)) - internal_errorf("shf_ungetc: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_ungetc", shf->flags); if ((shf->flags & SHF_ERROR) || c == EOF || (shf->rp == shf->buf && shf->rnleft)) @@ -542,8 +548,9 @@ shf_ungetc(int c, struct shf *shf) if (shf->rp == shf->buf) shf->rp = shf->buf + shf->rbsize; if (shf->flags & SHF_STRING) { - /* Can unget what was read, but not something different - we - * don't want to modify a string. + /* + * Can unget what was read, but not something different; + * we don't want to modify a string. */ if (shf->rp[-1] != c) return (EOF); @@ -558,26 +565,27 @@ shf_ungetc(int c, struct shf *shf) return (c); } -/* Write a character. Returns the character if successful, EOF if - * the char could not be written. +/* + * Write a character. Returns the character if successful, EOF if the + * char could not be written. */ int shf_putchar(int c, struct shf *shf) { if (!(shf->flags & SHF_WR)) - internal_errorf("shf_putchar: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_putchar", shf->flags); if (c == EOF) return (EOF); if (shf->flags & SHF_UNBUF) { unsigned char cc = (unsigned char)c; - int n; + ssize_t n; if (shf->fd < 0) - internal_errorf("shf_putchar: no fd"); + internal_errorf("%s: %s", "shf_putchar", "no fd"); if (shf->flags & SHF_ERROR) { - errno = shf->errno_; + errno = shf->errnosv; return (EOF); } while ((n = write(shf->fd, &cc, 1)) != 1) @@ -586,7 +594,7 @@ shf_putchar(int c, struct shf *shf) !(shf->flags & SHF_INTERRUPT)) continue; shf->flags |= SHF_ERROR; - shf->errno_ = errno; + shf->errnosv = errno; return (EOF); } } else { @@ -600,10 +608,11 @@ shf_putchar(int c, struct shf *shf) return (c); } -/* Write a string. Returns the length of the string if successful, EOF if - * the string could not be written. +/* + * Write a string. Returns the length of the string if successful, EOF + * if the string could not be written. */ -int +ssize_t shf_puts(const char *s, struct shf *shf) { if (!s) @@ -613,16 +622,16 @@ shf_puts(const char *s, struct shf *shf) } /* Write a buffer. Returns nbytes if successful, EOF if there is an error. */ -int -shf_write(const char *buf, int nbytes, struct shf *shf) +ssize_t +shf_write(const char *buf, ssize_t nbytes, struct shf *shf) { - int n, ncopy, orig_nbytes = nbytes; + ssize_t n, ncopy, orig_nbytes = nbytes; if (!(shf->flags & SHF_WR)) - internal_errorf("shf_write: flags %x", shf->flags); + internal_errorf("%s: flags 0x%X", "shf_write", shf->flags); if (nbytes < 0) - internal_errorf("shf_write: nbytes %d", nbytes); + internal_errorf("%s: %s %zd", "shf_write", "nbytes", nbytes); /* Don't buffer if buffer is empty and we're writting a large amount. */ if ((ncopy = shf->wnleft) && @@ -659,7 +668,7 @@ shf_write(const char *buf, int nbytes, struct shf *shf) !(shf->flags & SHF_INTERRUPT)) continue; shf->flags |= SHF_ERROR; - shf->errno_ = errno; + shf->errnosv = errno; shf->wnleft = 0; /* * Note: fwrite(3) returns 0 @@ -684,11 +693,11 @@ shf_write(const char *buf, int nbytes, struct shf *shf) return (orig_nbytes); } -int +ssize_t shf_fprintf(struct shf *shf, const char *fmt, ...) { va_list args; - int n; + ssize_t n; va_start(args, fmt); n = shf_vfprintf(shf, fmt, args); @@ -697,21 +706,23 @@ shf_fprintf(struct shf *shf, const char *fmt, ...) return (n); } -int -shf_snprintf(char *buf, int bsize, const char *fmt, ...) +ssize_t +shf_snprintf(char *buf, ssize_t bsize, const char *fmt, ...) { struct shf shf; va_list args; - int n; + ssize_t n; if (!buf || bsize <= 0) - internal_errorf("shf_snprintf: buf %p, bsize %d", buf, bsize); + internal_errorf("shf_snprintf: buf %zX, bsize %zd", + (size_t)buf, bsize); shf_sopen(buf, bsize, SHF_WR, &shf); va_start(args, fmt); n = shf_vfprintf(&shf, fmt, args); va_end(args); - shf_sclose(&shf); /* null terminates */ + /* NUL terminates */ + shf_sclose(&shf); return (n); } @@ -725,20 +736,11 @@ shf_smprintf(const char *fmt, ...) va_start(args, fmt); shf_vfprintf(&shf, fmt, args); va_end(args); - return (shf_sclose(&shf)); /* null terminates */ + /* NUL terminates */ + return (shf_sclose(&shf)); } -#undef FP /* if you want floating point stuff */ - -#ifndef DMAXEXP -# define DMAXEXP 128 /* should be big enough */ -#endif - #define BUF_SIZE 128 -/* must be > MAX(DMAXEXP, log10(pow(2, DSIGNIF))) + ceil(log10(DMAXEXP)) + 8 - * (I think); since it's hard to express as a constant, just use a large buffer - */ -#define FPBUF_SIZE (DMAXEXP+16) #define FL_HASH 0x001 /* '#' seen */ #define FL_PLUS 0x002 /* '+' seen */ @@ -750,19 +752,23 @@ shf_smprintf(const char *fmt, ...) #define FL_DOT 0x080 /* '.' seen */ #define FL_UPPER 0x100 /* format character was uppercase */ #define FL_NUMBER 0x200 /* a number was formated %[douxefg] */ +#define FL_SIZET 0x400 /* 'z' seen */ +#define FM_SIZES 0x430 /* h/l/z mask */ - -int +ssize_t shf_vfprintf(struct shf *shf, const char *fmt, va_list args) { const char *s; char c, *cp; - int tmp = 0, field, precision, len, flags; + int tmp = 0, flags; + ssize_t field, precision, len; unsigned long lnum; /* %#o produces the longest output */ char numbuf[(8 * sizeof(long) + 2) / 3 + 1]; /* this stuff for dealing with the buffer */ - int nwritten = 0; + ssize_t nwritten = 0; + +#define VA(type) va_arg(args, type) if (!fmt) return (0); @@ -774,13 +780,14 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) continue; } /* - * This will accept flags/fields in any order - not - * just the order specified in printf(3), but this is - * the way _doprnt() seems to work (on bsd and sysV). - * The only restriction is that the format character must - * come last :-). + * This will accept flags/fields in any order - not just + * the order specified in printf(3), but this is the way + * _doprnt() seems to work (on BSD and SYSV). The only + * restriction is that the format character must come + * last :-). */ - flags = field = precision = 0; + flags = 0; + field = precision = 0; for ( ; (c = *fmt++) ; ) { switch (c) { case '#': @@ -810,7 +817,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) continue; case '*': - tmp = va_arg(args, int); + tmp = VA(int); if (flags & FL_DOT) precision = tmp; else if ((field = tmp) < 0) { @@ -820,19 +827,27 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) continue; case 'l': + flags &= ~FM_SIZES; flags |= FL_LONG; continue; case 'h': + flags &= ~FM_SIZES; flags |= FL_SHORT; continue; + + case 'z': + flags &= ~FM_SIZES; + flags |= FL_SIZET; + continue; } if (ksh_isdigit(c)) { tmp = c - '0'; while (c = *fmt++, ksh_isdigit(c)) tmp = tmp * 10 + c - '0'; --fmt; - if (tmp < 0) /* overflow? */ + if (tmp < 0) + /* overflow? */ tmp = 0; if (flags & FL_DOT) precision = tmp; @@ -846,7 +861,8 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) if (precision < 0) precision = 0; - if (!c) /* nasty format */ + if (!c) + /* nasty format */ break; if (c >= 'A' && c <= 'Z') { @@ -855,33 +871,34 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) } switch (c) { - case 'p': /* pointer */ - flags &= ~(FL_LONG | FL_SHORT); - flags |= (sizeof(char *) > sizeof(int)) ? - /* hope it fits.. */ FL_LONG : 0; - /* aaahhh... */ case 'd': case 'i': + if (flags & FL_SIZET) + lnum = (long)VA(ssize_t); + else if (flags & FL_LONG) + lnum = VA(long); + else if (flags & FL_SHORT) + lnum = (long)(short)VA(int); + else + lnum = (long)VA(int); + goto integral; + case 'o': case 'u': case 'x': + if (flags & FL_SIZET) + lnum = VA(size_t); + else if (flags & FL_LONG) + lnum = VA(unsigned long); + else if (flags & FL_SHORT) + lnum = (unsigned long)(unsigned short)VA(int); + else + lnum = (unsigned long)VA(unsigned int); + + integral: flags |= FL_NUMBER; cp = numbuf + sizeof(numbuf); - /*- - * XXX any better way to do this? - * XXX hopefully the compiler optimises this out - * - * For shorts, we want sign extend for %d but not - * for %[oxu] - on 16 bit machines it doesn't matter. - * Assumes C compiler has converted shorts to ints - * before pushing them. XXX optimise this -tg - */ - if (flags & FL_LONG) - lnum = va_arg(args, unsigned long); - else if ((sizeof(int) < sizeof(long)) && (c == 'd')) - lnum = (long)va_arg(args, int); - else - lnum = va_arg(args, unsigned int); + switch (c) { case 'd': case 'i': @@ -917,7 +934,6 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) *--cp = '0'; break; - case 'p': case 'x': { const char *digits = (flags & FL_UPPER) ? digits_uc : digits_lc; @@ -938,19 +954,20 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) field = precision; flags |= FL_ZERO; } else - precision = len; /* no loss */ + /* no loss */ + precision = len; } break; case 's': - if (!(s = va_arg(args, const char *))) + if ((s = VA(const char *)) == NULL) s = "(null)"; len = utf_mbswidth(s); break; case 'c': flags &= ~FL_DOT; - numbuf[0] = (char)(va_arg(args, int)); + numbuf[0] = (char)(VA(int)); s = numbuf; len = 1; break; @@ -1029,14 +1046,12 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) int shf_getc(struct shf *shf) { - return ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ : - shf_getchar(shf)); + return (shf_getc_(shf)); } int shf_putc(int c, struct shf *shf) { - return ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) : - ((shf)->wnleft--, *(shf)->wp++ = (c))); + return (shf_putc_(c, shf)); } #endif diff --git a/src/strlcpy.c b/src/strlcpy.c new file mode 100644 index 0000000..53f9130 --- /dev/null +++ b/src/strlcpy.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2006, 2008, 2009 + * Thorsten Glaser <tg@mirbsd.org> + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/strlcpy.c,v 1.7 2009/06/10 18:12:50 tg Rel $"); + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + const char *s = src; + + if (siz == 0) + goto traverse_src; + + /* copy as many chars as will fit */ + while (--siz && (*dst++ = *s++)) + ; + + /* not enough room in dst */ + if (siz == 0) { + /* safe to NUL-terminate dst since we copied <= siz-1 chars */ + *dst = '\0'; + traverse_src: + /* traverse rest of src */ + while (*s++) + ; + } + + /* count does not include NUL */ + return ((size_t)(s - src - 1)); +} @@ -1,7 +1,7 @@ /* $OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +22,10 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.49 2010/07/17 22:09:39 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.69 2011/09/07 15:24:21 tg Exp $"); + +extern short subshell_nesting_level; +extern void yyskiputf8bom(void); struct nesting_state { int start_token; /* token than began nesting (eg, FOR) */ @@ -32,7 +35,7 @@ struct nesting_state { static void yyparse(void); static struct op *pipeline(int); static struct op *andor(void); -static struct op *c_list(int); +static struct op *c_list(bool); static struct ioword *synio(int); static struct op *nested(int, int, int); static struct op *get_command(int); @@ -59,14 +62,14 @@ static void dbtestp_error(Test_env *, int, const char *) MKSH_A_NORETURN; static struct op *outtree; /* yyparse output */ static struct nesting_state nesting; /* \n changed to ; */ -static int reject; /* token(cf) gets symbol again */ -static int symbol; /* yylex value */ +static bool reject; /* token(cf) gets symbol again */ +static int symbol; /* yylex value */ -#define REJECT (reject = 1) -#define ACCEPT (reject = 0) +#define REJECT (reject = true) +#define ACCEPT (reject = false) #define token(cf) ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf))) #define tpeek(cf) ((reject) ? (symbol) : (REJECT, symbol = yylex(cf))) -#define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (0) +#define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (/* CONSTCOND */ 0) static void yyparse(void) @@ -122,20 +125,23 @@ andor(void) } static struct op * -c_list(int multi) +c_list(bool multi) { struct op *t = NULL, *p, *tl = NULL; - int c, have_sep; + int c; + bool have_sep; - while (1) { + while (/* CONSTCOND */ 1) { p = andor(); - /* Token has always been read/rejected at this point, so + /* + * Token has always been read/rejected at this point, so * we don't worry about what flags to pass token() */ c = token(0); - have_sep = 1; + have_sep = true; if (c == '\n' && (multi || inalias(source))) { - if (!p) /* ignore blank lines */ + if (!p) + /* ignore blank lines */ continue; } else if (!p) break; @@ -143,7 +149,7 @@ c_list(int multi) p = block(c == '&' ? TASYNC : TCOPROC, p, NOBLOCK, NOWORDS); else if (c != ';') - have_sep = 0; + have_sep = false; if (!t) t = p; else if (!tl) @@ -161,7 +167,7 @@ static struct ioword * synio(int cf) { struct ioword *iop; - static struct ioword *nextiop = NULL; + static struct ioword *nextiop; bool ishere; if (nextiop != NULL) { @@ -174,14 +180,18 @@ synio(int cf) return (NULL); ACCEPT; iop = yylval.iop; - ishere = (iop->flag&IOTYPE) == IOHERE; + if (iop->flag & IONDELIM) + goto gotnulldelim; + ishere = (iop->flag & IOTYPE) == IOHERE; musthave(LWORD, ishere ? HEREDELIM : 0); if (ishere) { iop->delim = yylval.cp; - if (*ident != 0) /* unquoted */ + if (*ident != 0) + /* unquoted */ + gotnulldelim: iop->flag |= IOEVAL; if (herep > &heres[HERES - 1]) - yyerror("too many <<s\n"); + yyerror("too many %ss\n", "<<"); *herep++ = iop; } else iop->name = yylval.cp; @@ -231,7 +241,8 @@ get_command(int cf) XPtrV args, vars; struct nesting_state old_nesting; - iops = alloc((NUFILE + 1) * sizeof(struct ioword *), ATEMP); + /* NUFILE is small enough to leave this addition unchecked */ + iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP); XPinit(args, 16); XPinit(vars, 16); @@ -242,7 +253,8 @@ get_command(int cf) afree(iops, ATEMP); XPfree(args); XPfree(vars); - return (NULL); /* empty line */ + /* empty line */ + return (NULL); case LWORD: case REDIR: @@ -250,21 +262,23 @@ get_command(int cf) syniocf &= ~(KEYWORD|ALIAS); t = newtp(TCOM); t->lineno = source->line; - while (1) { + while (/* CONSTCOND */ 1) { cf = (t->u.evalflags ? ARRAYVAR : 0) | (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD); switch (tpeek(cf)) { case REDIR: while ((iop = synio(cf)) != NULL) { if (iopn >= NUFILE) - yyerror("too many redirections\n"); + yyerror("too many %ss\n", + "redirection"); iops[iopn++] = iop; } break; case LWORD: ACCEPT; - /* the iopn == 0 and XPsize(vars) == 0 are + /* + * the iopn == 0 and XPsize(vars) == 0 are * dubious but AT&T ksh acts this way */ if (iopn == 0 && XPsize(vars) == 0 && @@ -279,7 +293,13 @@ get_command(int cf) break; case '(': - /* Check for "> foo (echo hi)" which AT&T ksh +#ifndef MKSH_SMALL + if ((XPsize(args) == 0 || Flag(FKEYWORD)) && + XPsize(vars) == 1 && is_wdvarassign(yylval.cp)) + goto is_wdarrassign; +#endif + /* + * Check for "> foo (echo hi)" which AT&T ksh * allows (not POSIX, but not disallowed) */ afree(t, ATEMP); @@ -287,55 +307,50 @@ get_command(int cf) ACCEPT; goto Subshell; } -#ifndef MKSH_SMALL - if ((XPsize(args) == 0 || Flag(FKEYWORD)) && - XPsize(vars) == 1 && is_wdvarassign(yylval.cp)) - goto is_wdarrassign; -#endif - /* Must be a function */ + + /* must be a function */ if (iopn != 0 || XPsize(args) != 1 || XPsize(vars) != 0) syntaxerr(NULL); ACCEPT; - /*(*/ - musthave(')', 0); + musthave(/*(*/')', 0); t = function_body(XPptrv(args)[0], false); goto Leave; #ifndef MKSH_SMALL is_wdarrassign: { static const char set_cmd0[] = { - CHAR, 'e', CHAR, 'v', - CHAR, 'a', CHAR, 'l', EOS + CHAR, 's', CHAR, 'e', + CHAR, 't', EOS }; static const char set_cmd1[] = { - CHAR, 's', CHAR, 'e', - CHAR, 't', CHAR, ' ', CHAR, '-', CHAR, 'A', EOS }; static const char set_cmd2[] = { CHAR, '-', CHAR, '-', EOS }; char *tcp; - XPfree(vars); - XPinit(vars, 16); - /* - * we know (or rather hope) that yylval.cp - * contains a string "varname=" - */ - tcp = wdcopy(yylval.cp, ATEMP); - tcp[wdscan(tcp, EOS) - tcp - 3] = EOS; - /* now make an array assignment command */ - t = newtp(TCOM); - t->lineno = source->line; + ACCEPT; + + /* manipulate the vars string */ + tcp = *(--vars.cur); + /* 'varname=' -> 'varname' */ + tcp[wdscan(tcp, EOS) - tcp - 3] = EOS; + + /* construct new args strings */ XPput(args, wdcopy(set_cmd0, ATEMP)); XPput(args, wdcopy(set_cmd1, ATEMP)); XPput(args, tcp); XPput(args, wdcopy(set_cmd2, ATEMP)); - musthave(LWORD,LETARRAY); - XPput(args, yylval.cp); - break; + + /* slurp in words till closing paren */ + while (token(CONTIN) == LWORD) + XPput(args, yylval.cp); + if (symbol != /*(*/ ')') + syntaxerr(NULL); + + goto Leave; } #endif @@ -348,7 +363,9 @@ get_command(int cf) case '(': Subshell: + ++subshell_nesting_level; t = nested(TPAREN, '(', ')'); + --subshell_nesting_level; break; case '{': /*}*/ @@ -362,13 +379,13 @@ get_command(int cf) CHAR, 't', EOS }; - /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ + /* leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ lno = source->line; ACCEPT; switch (token(LETEXPR)) { case LWORD: break; - case '(': /* ) */ + case '(': /*)*/ goto Subshell; default: syntaxerr(NULL); @@ -381,7 +398,7 @@ get_command(int cf) } case DBRACKET: /* [[ .. ]] */ - /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ + /* leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ t = newtp(TDBRACKET); ACCEPT; { @@ -403,8 +420,8 @@ get_command(int cf) t = newtp((c == FOR) ? TFOR : TSELECT); musthave(LWORD, ARRAYVAR); if (!is_wdvarname(yylval.cp, true)) - yyerror("%s: bad identifier\n", - c == FOR ? "for" : "select"); + yyerror("%s: %s\n", c == FOR ? "for" : Tselect, + "bad identifier"); strdupx(t->str, ident, ATEMP); nesting_push(&old_nesting, c); t->vars = wordlist(); @@ -452,7 +469,8 @@ get_command(int cf) t = pipeline(0); if (t) { t->str = alloc(2, ATEMP); - t->str[0] = '\0'; /* TF_* flags */ + /* TF_* flags */ + t->str[0] = '\0'; t->str[1] = '\0'; } t = block(TTIME, t, NOBLOCK, NOWORDS); @@ -466,7 +484,7 @@ get_command(int cf) while ((iop = synio(syniocf)) != NULL) { if (iopn >= NUFILE) - yyerror("too many redirections\n"); + yyerror("too many %ss\n", "redirection"); iops[iopn++] = iop; } @@ -475,7 +493,7 @@ get_command(int cf) t->ioact = NULL; } else { iops[iopn++] = NULL; - iops = aresize(iops, iopn * sizeof(struct ioword *), ATEMP); + iops = aresize2(iops, iopn, sizeof(struct ioword *), ATEMP); t->ioact = iops; } @@ -499,7 +517,8 @@ dogroup(void) struct op *list; c = token(CONTIN|KEYWORD|ALIAS); - /* A {...} can be used instead of do...done for for/select loops + /* + * A {...} can be used instead of do...done for for/select loops * but not for while/until loops - we don't need to check if it * is a while loop because it would have been parsed as part of * the conditional command list... @@ -567,7 +586,8 @@ caselist(void) else syntaxerr(NULL); t = tl = NULL; - while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */ + /* no ALIAS here */ + while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { struct op *tc = casepart(c); if (tl == NULL) t = tl = tc, tl->right = NULL; @@ -601,53 +621,62 @@ casepart(int endtok) t->left = c_list(true); /* Note: POSIX requires the ;; */ if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) - musthave(BREAK, CONTIN|KEYWORD|ALIAS); + switch (symbol) { + default: + syntaxerr(NULL); + case BREAK: + case BRKEV: + case BRKFT: + t->u.charflag = + (symbol == BRKEV) ? '|' : + (symbol == BRKFT) ? '&' : ';'; + ACCEPT; + } return (t); } static struct op * function_body(char *name, - bool ksh_func) /* function foo { ... } vs foo() { .. } */ + /* function foo { ... } vs foo() { .. } */ + bool ksh_func) { char *sname, *p; struct op *t; bool old_func_parse; - sname = wdstrip(name, false, false); - /* Check for valid characters in name. POSIX and AT&T ksh93 say only - * allow [a-zA-Z_0-9] but this allows more as old pdkshs have - * allowed more (the following were never allowed: + sname = wdstrip(name, 0); + /*- + * Check for valid characters in name. POSIX and AT&T ksh93 say + * only allow [a-zA-Z_0-9] but this allows more as old pdkshs + * have allowed more; the following were never allowed: * NUL TAB NL SP " $ & ' ( ) ; < = > \ ` | * C_QUOTE covers all but adds # * ? [ ] */ for (p = sname; *p; p++) if (ctype(*p, C_QUOTE)) - yyerror("%s: invalid function name\n", sname); + yyerror("%s: %s\n", sname, "invalid function name"); - /* Note that POSIX allows only compound statements after foo(), sh and - * AT&T ksh allow any command, go with the later since it shouldn't - * break anything. However, for function foo, AT&T ksh only accepts - * an open-brace. + /* + * Note that POSIX allows only compound statements after foo(), + * sh and AT&T ksh allow any command, go with the later since it + * shouldn't break anything. However, for function foo, AT&T ksh + * only accepts an open-brace. */ if (ksh_func) { - if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /* ) */) { - struct tbl *tp; - + if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /*)*/) { /* function foo () { */ ACCEPT; musthave(')', 0); /* degrade to POSIX function */ ksh_func = false; - if ((tp = ktsearch(&aliases, sname, hash(sname)))) - ktdelete(tp); } - musthave('{', CONTIN|KEYWORD|ALIAS); /* } */ + musthave('{' /*}*/, CONTIN|KEYWORD|ALIAS); REJECT; } t = newtp(TFUNCT); t->str = sname; - t->u.ksh_func = ksh_func; + t->u.ksh_func = tobool(ksh_func); t->lineno = source->line; old_func_parse = e->flags & EF_FUNC_PARSE; @@ -655,12 +684,13 @@ function_body(char *name, if ((t->left = get_command(CONTIN)) == NULL) { char *tv; /* - * Probably something like foo() followed by eof or ;. + * Probably something like foo() followed by EOF or ';'. * This is accepted by sh and ksh88. * To make "typeset -f foo" work reliably (so its output can * be used as input), we pretend there is a colon here. */ t->left = newtp(TCOM); + /* (2 * sizeof(char *)) is small enough */ t->left->args = alloc(2 * sizeof(char *), ATEMP); t->left->args[0] = tv = alloc(3, ATEMP); tv[0] = CHAR; @@ -686,7 +716,8 @@ wordlist(void) XPinit(args, 16); /* POSIX does not do alias expansion here... */ if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) { - if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */ + if (c != ';') + /* non-POSIX, but AT&T ksh accepts a ; here */ REJECT; return (NULL); } @@ -733,13 +764,13 @@ const struct tokeninfo { { "case", CASE, true }, { "esac", ESAC, true }, { "for", FOR, true }, - { "select", SELECT, true }, + { Tselect, SELECT, true }, { "while", WHILE, true }, { "until", UNTIL, true }, { "do", DO, true }, { "done", DONE, true }, { "in", IN, true }, - { "function", FUNCTION, true }, + { Tfunction, FUNCTION, true }, { "time", TIME, true }, { "{", '{', true }, { "}", '}', true }, @@ -749,6 +780,8 @@ const struct tokeninfo { { "&&", LOGAND, false }, { "||", LOGOR, false }, { ";;", BREAK, false }, + { ";|", BRKEV, false }, + { ";&", BRKFT, false }, { "((", MDPAREN, false }, { "|&", COPROC, false }, /* and some special cases... */ @@ -762,8 +795,9 @@ initkeywords(void) struct tokeninfo const *tt; struct tbl *p; - ktinit(&keywords, APERM, - /* must be 80% of 2^n (currently 20 keywords) */ 32); + ktinit(APERM, &keywords, + /* currently 28 keywords -> 80% of 64 (2^6) */ + 6); for (tt = tokentab; tt->name; tt++) { if (tt->reserved) { p = ktenter(&keywords, tt->name, hash(tt->name)); @@ -777,7 +811,8 @@ initkeywords(void) static void syntaxerr(const char *what) { - char redir[6]; /* 2<<- is the longest redirection, I think */ + /* 2<<- is the longest redirection, I think */ + char redir[6]; const char *s; struct tokeninfo const *tt; int c; @@ -796,7 +831,7 @@ syntaxerr(const char *what) goto Again; } /* don't quote the EOF */ - yyerror("%s: unexpected EOF\n", T_synerr); + yyerror("%s: %s %s\n", Tsynerr, "unexpected", "EOF"); /* NOTREACHED */ case LWORD: @@ -823,7 +858,7 @@ syntaxerr(const char *what) s = redir; } } - yyerror("%s: '%s' %s\n", T_synerr, s, what); + yyerror("%s: '%s' %s\n", Tsynerr, s, what); } static void @@ -857,17 +892,20 @@ newtp(int type) } struct op * -compile(Source *s) +compile(Source *s, bool skiputf8bom) { nesting.start_token = 0; nesting.start_line = 0; herep = heres; source = s; + if (skiputf8bom) + yyskiputf8bom(); yyparse(); return (outtree); } -/* This kludge exists to take care of sh/AT&T ksh oddity in which +/*- + * This kludge exists to take care of sh/AT&T ksh oddity in which * the arguments of alias/export/readonly/typeset have no field * splitting, file globbing, or (normal) tilde expansion done. * AT&T ksh seems to do something similar to this since @@ -882,10 +920,10 @@ assign_command(char *s) { if (!*s) return (0); - return ((strcmp(s, "alias") == 0) || + return ((strcmp(s, Talias) == 0) || (strcmp(s, "export") == 0) || (strcmp(s, "readonly") == 0) || - (strcmp(s, T_typeset) == 0)); + (strcmp(s, Ttypeset) == 0)); } /* Check if we are in the middle of reading an alias */ @@ -899,7 +937,8 @@ inalias(struct source *s) } -/* Order important - indexed by Test_meta values +/* + * Order important - indexed by Test_meta values * Note that ||, &&, ( and ) can't appear in as unquoted strings * in normal shell input, so these can be interpreted unambiguously * in the evaluation pass. @@ -952,7 +991,8 @@ dbtestp_isa(Test_env *te, Test_meta meta) db_lthan : db_gthan, ATEMP); } else if (uqword && (ret = test_isop(meta, ident))) save = yylval.cp; - } else /* meta == TM_END */ + } else + /* meta == TM_END */ ret = (uqword && !strcmp(yylval.cp, db_close)) ? TO_NONNULL : TO_NONOP; if (ret != TO_NONOP) { @@ -1002,3 +1042,96 @@ dbtestp_error(Test_env *te, int offset, const char *msg) } syntaxerr(msg); } + +#if HAVE_SELECT + +#ifndef EOVERFLOW +#ifdef ERANGE +#define EOVERFLOW ERANGE +#else +#define EOVERFLOW EINVAL +#endif +#endif + +bool +parse_usec(const char *s, struct timeval *tv) +{ + struct timeval tt; + int i; + + tv->tv_sec = 0; + /* parse integral part */ + while (ksh_isdigit(*s)) { + tt.tv_sec = tv->tv_sec * 10 + (*s++ - '0'); + if (tt.tv_sec / 10 != tv->tv_sec) { + errno = EOVERFLOW; + return (true); + } + tv->tv_sec = tt.tv_sec; + } + + tv->tv_usec = 0; + if (!*s) + /* no decimal fraction */ + return (false); + else if (*s++ != '.') { + /* junk after integral part */ + errno = EINVAL; + return (true); + } + + /* parse decimal fraction */ + i = 100000; + while (ksh_isdigit(*s)) { + tv->tv_usec += i * (*s++ - '0'); + if (i == 1) + break; + i /= 10; + } + /* check for junk after fractional part */ + while (ksh_isdigit(*s)) + ++s; + if (*s) { + errno = EINVAL; + return (true); + } + + /* end of input string reached, no errors */ + return (false); +} +#endif + +/* + * Helper function called from within lex.c:yylex() to parse + * a COMSUB recursively using the main shell parser and lexer + */ +char * +yyrecursive(void) +{ + struct op *t; + char *cp; + bool old_reject; + int old_symbol; + struct ioword **old_herep; + + /* tell the lexer to accept a closing parenthesis as EOD */ + ++subshell_nesting_level; + + /* push reject state, parse recursively, pop reject state */ + old_reject = reject; + old_symbol = symbol; + ACCEPT; + old_herep = herep; + /* we use TPAREN as a helper container here */ + t = nested(TPAREN, '(', ')'); + herep = old_herep; + reject = old_reject; + symbol = old_symbol; + + /* t->left because nested(TPAREN, ...) hides our goodies there */ + cp = snptreef(NULL, 0, "%T", t->left); + tfree(t, ATEMP); + + --subshell_nesting_level; + return (cp); +} @@ -1,7 +1,7 @@ /* $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -22,19 +22,20 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.51 2011/09/07 15:24:21 tg Exp $"); -#define INDENT 4 +#define INDENT 8 -#define tputc(c, shf) shf_putchar(c, shf); static void ptree(struct op *, int, struct shf *); static void pioact(struct shf *, int, struct ioword *); -static void tputC(int, struct shf *); -static void tputS(char *, struct shf *); +static const char *wdvarput(struct shf *, const char *, int, int); static void vfptreef(struct shf *, int, const char *, va_list); static struct ioword **iocopy(struct ioword **, Area *); static void iofree(struct ioword **, Area *); +/* "foo& ; bar" and "foo |& ; bar" are invalid */ +static bool prevent_semicolon; + /* * print a command tree */ @@ -44,22 +45,26 @@ ptree(struct op *t, int indent, struct shf *shf) const char **w; struct ioword **ioact; struct op *t1; + int i; Chain: if (t == NULL) return; switch (t->type) { case TCOM: - if (t->vars) - for (w = (const char **)t->vars; *w != NULL; ) + if (t->vars) { + w = (const char **)t->vars; + while (*w) fptreef(shf, indent, "%S ", *w++); - else + } else shf_puts("#no-vars# ", shf); - if (t->args) - for (w = t->args; *w != NULL; ) + if (t->args) { + w = t->args; + while (*w) fptreef(shf, indent, "%S ", *w++); - else + } else shf_puts("#no-args# ", shf); + prevent_semicolon = false; break; case TEXEC: t = t->left; @@ -78,30 +83,28 @@ ptree(struct op *t, int indent, struct shf *shf) case TOR: case TAND: fptreef(shf, indent, "%T%s %T", - t->left, (t->type==TOR) ? "||" : "&&", t->right); + t->left, (t->type == TOR) ? "||" : "&&", t->right); break; case TBANG: shf_puts("! ", shf); + prevent_semicolon = false; t = t->right; goto Chain; - case TDBRACKET: { - int i; - + case TDBRACKET: + w = t->args; shf_puts("[[", shf); - for (i = 0; t->args[i]; i++) - fptreef(shf, indent, " %S", t->args[i]); + while (*w) + fptreef(shf, indent, " %S", *w++); shf_puts(" ]] ", shf); break; - } case TSELECT: - fptreef(shf, indent, "select %s ", t->str); - /* FALLTHROUGH */ case TFOR: - if (t->type == TFOR) - fptreef(shf, indent, "for %s ", t->str); + fptreef(shf, indent, "%s %s ", + (t->type == TFOR) ? "for" : Tselect, t->str); if (t->vars != NULL) { shf_puts("in ", shf); - for (w = (const char **)t->vars; *w; ) + w = (const char **)t->vars; + while (*w) fptreef(shf, indent, "%S ", *w++); fptreef(shf, indent, "%;"); } @@ -112,34 +115,43 @@ ptree(struct op *t, int indent, struct shf *shf) fptreef(shf, indent, "case %S in", t->str); for (t1 = t->left; t1 != NULL; t1 = t1->right) { fptreef(shf, indent, "%N("); - for (w = (const char **)t1->vars; *w != NULL; w++) + w = (const char **)t1->vars; + while (*w) { fptreef(shf, indent, "%S%c", *w, (w[1] != NULL) ? '|' : ')'); - fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left); + ++w; + } + fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left, + t1->u.charflag); } fptreef(shf, indent, "%Nesac "); break; - case TIF: +#ifndef MKSH_NO_DEPRECATED_WARNING case TELIF: - /* 3 == strlen("if ") */ - fptreef(shf, indent + 3, "if %T", t->left); - for (;;) { + internal_errorf("TELIF in tree.c:ptree() unexpected"); + /* FALLTHROUGH */ +#endif + case TIF: + i = 2; + goto process_TIF; + do { + t = t->right; + i = 0; + fptreef(shf, indent, "%;"); + process_TIF: + /* 5 == strlen("elif ") */ + fptreef(shf, indent + 5 - i, "elif %T" + i, t->left); t = t->right; if (t->left != NULL) { fptreef(shf, indent, "%;"); - fptreef(shf, indent + INDENT, "then%N%T", - t->left); + fptreef(shf, indent + INDENT, "%s%N%T", + "then", t->left); } - if (t->right == NULL || t->right->type != TELIF) - break; - t = t->right; - fptreef(shf, indent, "%;"); - /* 5 == strlen("elif ") */ - fptreef(shf, indent + 5, "elif %T", t->left); - } + } while (t->right && t->right->type == TELIF); if (t->right != NULL) { fptreef(shf, indent, "%;"); - fptreef(shf, indent + INDENT, "else%;%T", t->right); + fptreef(shf, indent + INDENT, "%s%N%T", + "else", t->right); } fptreef(shf, indent, "%;fi "); break; @@ -147,60 +159,63 @@ ptree(struct op *t, int indent, struct shf *shf) case TUNTIL: /* 6 == strlen("while"/"until") */ fptreef(shf, indent + 6, "%s %T", - (t->type==TWHILE) ? "while" : "until", + (t->type == TWHILE) ? "while" : "until", t->left); - fptreef(shf, indent, "%;do"); - fptreef(shf, indent + INDENT, "%;%T", t->right); + fptreef(shf, indent, "%;"); + fptreef(shf, indent + INDENT, "do%N%T", t->right); fptreef(shf, indent, "%;done "); break; case TBRACE: - fptreef(shf, indent + INDENT, "{%;%T", t->left); + fptreef(shf, indent + INDENT, "{%N%T", t->left); fptreef(shf, indent, "%;} "); break; case TCOPROC: fptreef(shf, indent, "%T|& ", t->left); + prevent_semicolon = true; break; case TASYNC: fptreef(shf, indent, "%T& ", t->left); + prevent_semicolon = true; break; case TFUNCT: - fptreef(shf, indent, - t->u.ksh_func ? "function %s %T" : "%s() %T", - t->str, t->left); + fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left); break; case TTIME: - fptreef(shf, indent, "time %T", t->left); + fptreef(shf, indent, "%s %T", "time", t->left); break; default: shf_puts("<botch>", shf); + prevent_semicolon = false; break; } if ((ioact = t->ioact) != NULL) { - int need_nl = 0; + bool need_nl = false; while (*ioact != NULL) pioact(shf, indent, *ioact++); /* Print here documents after everything else... */ - for (ioact = t->ioact; *ioact != NULL; ) { + ioact = t->ioact; + while (*ioact != NULL) { struct ioword *iop = *ioact++; - /* heredoc is 0 when tracing (set -x) */ - if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc && - /* iop->delim[1] == '<' means here string */ - (!iop->delim || iop->delim[1] != '<')) { - tputc('\n', shf); + /* heredoc is NULL when tracing (set -x) */ + if ((iop->flag & (IOTYPE | IOHERESTR)) == IOHERE && + iop->heredoc) { + shf_putc('\n', shf); shf_puts(iop->heredoc, shf); fptreef(shf, indent, "%s", + iop->flag & IONDELIM ? "<<" : evalstr(iop->delim, 0)); - need_nl = 1; + need_nl = true; } } - /* Last delimiter must be followed by a newline (this often - * leads to an extra blank line, but its not worth worrying - * about) + /* + * Last delimiter must be followed by a newline (this + * often leads to an extra blank line, but it's not + * worth worrying about) */ if (need_nl) - tputc('\n', shf); + shf_putc('\n', shf); } } @@ -220,123 +235,136 @@ pioact(struct shf *shf, int indent, struct ioword *iop) switch (type) { case IOREAD: - shf_puts("< ", shf); + shf_puts("<", shf); break; case IOHERE: shf_puts(flag & IOSKIP ? "<<-" : "<<", shf); break; case IOCAT: - shf_puts(">> ", shf); + shf_puts(">>", shf); break; case IOWRITE: - shf_puts(flag & IOCLOB ? ">| " : "> ", shf); + shf_puts(flag & IOCLOB ? ">|" : ">", shf); break; case IORDWR: - shf_puts("<> ", shf); + shf_puts("<>", shf); break; case IODUP: shf_puts(flag & IORDUP ? "<&" : ">&", shf); break; } - /* name/delim are 0 when printing syntax errors */ + /* name/delim are NULL when printing syntax errors */ if (type == IOHERE) { if (iop->delim) - fptreef(shf, indent, "%s%S ", - /* here string */ iop->delim[1] == '<' ? "" : " ", - iop->delim); + fptreef(shf, indent, "%S ", iop->delim); else - tputc(' ', shf); + shf_putc(' ', shf); } else if (iop->name) fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ", iop->name); + prevent_semicolon = false; } - -/* - * variants of fputc, fputs for ptreef and snptreef - */ -static void -tputC(int c, struct shf *shf) -{ - if ((c&0x60) == 0) { /* C0|C1 */ - tputc((c&0x80) ? '$' : '^', shf); - tputc(((c&0x7F)|0x40), shf); - } else if ((c&0x7F) == 0x7F) { /* DEL */ - tputc((c&0x80) ? '$' : '^', shf); - tputc('?', shf); - } else - tputc(c, shf); -} - -static void -tputS(char *wp, struct shf *shf) +/* variant of fputs for ptreef and wdstrip */ +static const char * +wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) { - int c, quotelevel = 0; + int c; - /* problems: + /*- + * problems: * `...` -> $(...) * 'foo' -> "foo" + * x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS + * x${foo:-'hi'} -> x${foo:-hi} unless WDS_KEEPQ * could change encoding to: * OQUOTE ["'] ... CQUOTE ["'] * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case) */ - while (1) + while (/* CONSTCOND */ 1) switch (*wp++) { case EOS: - return; + return (--wp); case ADELIM: case CHAR: - tputC(*wp++, shf); + c = *wp++; + if ((opmode & WDS_MAGIC) && + (ISMAGIC(c) || c == '[' || c == NOT || + c == '-' || c == ']' || c == '*' || c == '?')) + shf_putc(MAGIC, shf); + shf_putc(c, shf); break; - case QCHAR: + case QCHAR: { + bool doq; + c = *wp++; - if (!quotelevel || (c == '"' || c == '`' || c == '$')) - tputc('\\', shf); - tputC(c, shf); + doq = (c == '"' || c == '`' || c == '$' || c == '\\'); + if (opmode & WDS_TPUTS) { + if (quotelevel == 0) + doq = true; + } else { + if (!(opmode & WDS_KEEPQ)) + doq = false; + } + if (doq) + shf_putc('\\', shf); + shf_putc(c, shf); break; + } case COMSUB: shf_puts("$(", shf); - while (*wp != 0) - tputC(*wp++, shf); - tputc(')', shf); - wp++; + while ((c = *wp++) != 0) + shf_putc(c, shf); + shf_putc(')', shf); break; case EXPRSUB: shf_puts("$((", shf); - while (*wp != 0) - tputC(*wp++, shf); + while ((c = *wp++) != 0) + shf_putc(c, shf); shf_puts("))", shf); - wp++; break; case OQUOTE: - quotelevel++; - tputc('"', shf); + if (opmode & WDS_TPUTS) { + quotelevel++; + shf_putc('"', shf); + } break; case CQUOTE: - if (quotelevel) - quotelevel--; - tputc('"', shf); + if (opmode & WDS_TPUTS) { + if (quotelevel) + quotelevel--; + shf_putc('"', shf); + } break; case OSUBST: - tputc('$', shf); + shf_putc('$', shf); if (*wp++ == '{') - tputc('{', shf); + shf_putc('{', shf); while ((c = *wp++) != 0) - tputC(c, shf); + shf_putc(c, shf); + wp = wdvarput(shf, wp, 0, opmode); break; case CSUBST: if (*wp++ == '}') - tputc('}', shf); - break; + shf_putc('}', shf); + return (wp); case OPAT: - tputc(*wp++, shf); - tputc('(', shf); + if (opmode & WDS_MAGIC) { + shf_putc(MAGIC, shf); + shf_putchar(*wp++ | 0x80, shf); + } else { + shf_putchar(*wp++, shf); + shf_putc('(', shf); + } break; case SPAT: - tputc('|', shf); - break; + c = '|'; + if (0) case CPAT: - tputc(')', shf); + c = /*(*/ ')'; + if (opmode & WDS_MAGIC) + shf_putc(MAGIC, shf); + shf_putc(c, shf); break; } } @@ -346,21 +374,19 @@ tputS(char *wp, struct shf *shf) * variable args with an ANSI compiler */ /* VARARGS */ -int +void fptreef(struct shf *shf, int indent, const char *fmt, ...) { va_list va; va_start(va, fmt); - vfptreef(shf, indent, fmt, va); va_end(va); - return (0); } /* VARARGS */ char * -snptreef(char *s, int n, const char *fmt, ...) +snptreef(char *s, ssize_t n, const char *fmt, ...) { va_list va; struct shf shf; @@ -371,7 +397,8 @@ snptreef(char *s, int n, const char *fmt, ...) vfptreef(&shf, 0, fmt, va); va_end(va); - return (shf_sclose(&shf)); /* null terminates */ + /* shf_sclose NUL terminates */ + return (shf_sclose(&shf)); } static void @@ -383,48 +410,63 @@ vfptreef(struct shf *shf, int indent, const char *fmt, va_list va) if (c == '%') { switch ((c = *fmt++)) { case 'c': - tputc(va_arg(va, int), shf); + /* character (octet, probably) */ + shf_putchar(va_arg(va, int), shf); break; case 's': + /* string */ shf_puts(va_arg(va, char *), shf); break; - case 'S': /* word */ - tputS(va_arg(va, char *), shf); + case 'S': + /* word */ + wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS); break; - case 'd': /* decimal */ + case 'd': + /* signed decimal */ shf_fprintf(shf, "%d", va_arg(va, int)); break; - case 'u': /* decimal */ + case 'u': + /* unsigned decimal */ shf_fprintf(shf, "%u", va_arg(va, unsigned int)); break; - case 'T': /* format tree */ + case 'T': + /* format tree */ ptree(va_arg(va, struct op *), indent, shf); - break; - case ';': /* newline or ; */ - case 'N': /* newline or space */ + goto dont_trash_prevent_semicolon; + case ';': + /* newline or ; */ + case 'N': + /* newline or space */ if (shf->flags & SHF_STRING) { - if (c == ';') - tputc(';', shf); - tputc(' ', shf); + if (c == ';' && !prevent_semicolon) + shf_putc(';', shf); + shf_putc(' ', shf); } else { int i; - tputc('\n', shf); - for (i = indent; i >= 8; i -= 8) - tputc('\t', shf); - for (; i > 0; --i) - tputc(' ', shf); + shf_putc('\n', shf); + i = indent; + while (i >= 8) { + shf_putc('\t', shf); + i -= 8; + } + while (i--) + shf_putc(' ', shf); } break; case 'R': + /* I/O redirection */ pioact(shf, indent, va_arg(va, struct ioword *)); break; default: - tputc(c, shf); + shf_putc(c, shf); break; } } else - tputc(c, shf); + shf_putc(c, shf); + prevent_semicolon = false; + dont_trash_prevent_semicolon: + ; } } @@ -454,11 +496,13 @@ tcopy(struct op *t, Area *ap) if (t->vars == NULL) r->vars = NULL; else { - for (tw = (const char **)t->vars; *tw++ != NULL; ) - ; - rw = r->vars = alloc((tw - (const char **)t->vars + 1) * + tw = (const char **)t->vars; + while (*tw) + ++tw; + rw = r->vars = alloc2(tw - (const char **)t->vars + 1, sizeof(*tw), ap); - for (tw = (const char **)t->vars; *tw != NULL; ) + tw = (const char **)t->vars; + while (*tw) *rw++ = wdcopy(*tw++, ap); *rw = NULL; } @@ -466,11 +510,13 @@ tcopy(struct op *t, Area *ap) if (t->args == NULL) r->args = NULL; else { - for (tw = t->args; *tw++ != NULL; ) - ; - r->args = (const char **)(rw = alloc((tw - t->args + 1) * + tw = t->args; + while (*tw) + ++tw; + r->args = (const char **)(rw = alloc2(tw - t->args + 1, sizeof(*tw), ap)); - for (tw = t->args; *tw != NULL; ) + tw = t->args; + while (*tw) *rw++ = wdcopy(*tw++, ap); *rw = NULL; } @@ -487,7 +533,9 @@ tcopy(struct op *t, Area *ap) char * wdcopy(const char *wp, Area *ap) { - size_t len = wdscan(wp, EOS) - wp; + size_t len; + + len = wdscan(wp, EOS) - wp; return (memcpy(alloc(len, ap), wp, len)); } @@ -497,7 +545,7 @@ wdscan(const char *wp, int c) { int nest = 0; - while (1) + while (/* CONSTCOND */ 1) switch (*wp++) { case EOS: return (wp); @@ -546,88 +594,19 @@ wdscan(const char *wp, int c) } } -/* return a copy of wp without any of the mark up characters and - * with quote characters (" ' \) stripped. - * (string is allocated from ATEMP) +/* + * return a copy of wp without any of the mark up characters and with + * quote characters (" ' \) stripped. (string is allocated from ATEMP) */ char * -wdstrip(const char *wp, bool keepq, bool make_magic) +wdstrip(const char *wp, int opmode) { struct shf shf; - int c; shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf); - - /* problems: - * `...` -> $(...) - * x${foo:-"hi"} -> x${foo:-hi} - * x${foo:-'hi'} -> x${foo:-hi} unless keepq - */ - while (1) - switch (*wp++) { - case EOS: - return (shf_sclose(&shf)); /* null terminates */ - case ADELIM: - case CHAR: - c = *wp++; - if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT || - c == '-' || c == ']' || c == '*' || c == '?')) - shf_putchar(MAGIC, &shf); - shf_putchar(c, &shf); - break; - case QCHAR: - c = *wp++; - if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\')) - shf_putchar('\\', &shf); - shf_putchar(c, &shf); - break; - case COMSUB: - shf_puts("$(", &shf); - while (*wp != 0) - shf_putchar(*wp++, &shf); - shf_putchar(')', &shf); - break; - case EXPRSUB: - shf_puts("$((", &shf); - while (*wp != 0) - shf_putchar(*wp++, &shf); - shf_puts("))", &shf); - break; - case OQUOTE: - break; - case CQUOTE: - break; - case OSUBST: - shf_putchar('$', &shf); - if (*wp++ == '{') - shf_putchar('{', &shf); - while ((c = *wp++) != 0) - shf_putchar(c, &shf); - break; - case CSUBST: - if (*wp++ == '}') - shf_putchar('}', &shf); - break; - case OPAT: - if (make_magic) { - shf_putchar(MAGIC, &shf); - shf_putchar(*wp++ | 0x80, &shf); - } else { - shf_putchar(*wp++, &shf); - shf_putchar('(', &shf); - } - break; - case SPAT: - if (make_magic) - shf_putchar(MAGIC, &shf); - shf_putchar('|', &shf); - break; - case CPAT: - if (make_magic) - shf_putchar(MAGIC, &shf); - shf_putchar(')', &shf); - break; - } + wdvarput(&shf, wp, 0, opmode); + /* shf_sclose NUL terminates */ + return (shf_sclose(&shf)); } static struct ioword ** @@ -636,9 +615,10 @@ iocopy(struct ioword **iow, Area *ap) struct ioword **ior; int i; - for (ior = iow; *ior++ != NULL; ) - ; - ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap); + ior = iow; + while (*ior) + ++ior; + ior = alloc2(ior - iow + 1, sizeof(struct ioword *), ap); for (i = 0; iow[i] != NULL; i++) { struct ioword *p, *q; @@ -680,8 +660,9 @@ tfree(struct op *t, Area *ap) } if (t->args != NULL) { + /*XXX we assume the caller is right */ union mksh_ccphack cw; - /* XXX we assume the caller is right */ + cw.ro = t->args; for (w = cw.rw; *w != NULL; w++) afree(*w, ap); @@ -703,7 +684,8 @@ iofree(struct ioword **iow, Area *ap) struct ioword **iop; struct ioword *p; - for (iop = iow; (p = *iop++) != NULL; ) { + iop = iow; + while ((p = *iop++) != NULL) { if (p->name != NULL) afree(p->name, ap); if (p->delim != NULL) @@ -714,3 +696,315 @@ iofree(struct ioword **iow, Area *ap) } afree(iow, ap); } + +void +fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v) +{ + if (isksh) + fptreef(shf, i, "%s %s %T", Tfunction, k, v); + else + fptreef(shf, i, "%s() %T", k, v); +} + + +/* for jobs.c */ +void +vistree(char *dst, size_t sz, struct op *t) +{ + int c; + char *cp, *buf; + + buf = alloc(sz, ATEMP); + snptreef(buf, sz, "%T", t); + cp = buf; + while ((c = *cp++)) { + if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) { + /* C0 or C1 control character or DEL */ + if (!--sz) + break; + *dst++ = (c & 0x80) ? '$' : '^'; + c = (c & 0x7F) ^ 0x40; + } + if (!--sz) + break; + *dst++ = c; + } + *dst = '\0'; + afree(buf, ATEMP); +} + +#ifdef DEBUG +void +dumpchar(struct shf *shf, int c) +{ + if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) { + /* C0 or C1 control character or DEL */ + shf_putc((c & 0x80) ? '$' : '^', shf); + c = (c & 0x7F) ^ 0x40; + } + shf_putc(c, shf); +} + +/* see: wdvarput */ +static const char * +dumpwdvar_(struct shf *shf, const char *wp, int quotelevel) +{ + int c; + + while (/* CONSTCOND */ 1) { + switch(*wp++) { + case EOS: + shf_puts("EOS", shf); + return (--wp); + case ADELIM: + shf_puts("ADELIM=", shf); + if (0) + case CHAR: + shf_puts("CHAR=", shf); + dumpchar(shf, *wp++); + break; + case QCHAR: + shf_puts("QCHAR<", shf); + c = *wp++; + if (quotelevel == 0 || + (c == '"' || c == '`' || c == '$' || c == '\\')) + shf_putc('\\', shf); + dumpchar(shf, c); + goto closeandout; + case COMSUB: + shf_puts("COMSUB<", shf); + dumpsub: + while ((c = *wp++) != 0) + dumpchar(shf, c); + closeandout: + shf_putc('>', shf); + break; + case EXPRSUB: + shf_puts("EXPRSUB<", shf); + goto dumpsub; + case OQUOTE: + shf_fprintf(shf, "OQUOTE{%d", ++quotelevel); + break; + case CQUOTE: + shf_fprintf(shf, "%d}CQUOTE", quotelevel); + if (quotelevel) + quotelevel--; + else + shf_puts("(err)", shf); + break; + case OSUBST: + shf_puts("OSUBST(", shf); + dumpchar(shf, *wp++); + shf_puts(")[", shf); + while ((c = *wp++) != 0) + dumpchar(shf, c); + shf_putc('|', shf); + wp = dumpwdvar_(shf, wp, 0); + break; + case CSUBST: + shf_puts("]CSUBST(", shf); + dumpchar(shf, *wp++); + shf_putc(')', shf); + return (wp); + case OPAT: + shf_puts("OPAT=", shf); + dumpchar(shf, *wp++); + break; + case SPAT: + shf_puts("SPAT", shf); + break; + case CPAT: + shf_puts("CPAT", shf); + break; + default: + shf_fprintf(shf, "INVAL<%u>", (uint8_t)wp[-1]); + break; + } + shf_putc(' ', shf); + } +} +void +dumpwdvar(struct shf *shf, const char *wp) +{ + dumpwdvar_(shf, wp, 0); +} + +void +dumptree(struct shf *shf, struct op *t) +{ + int i; + const char **w, *name; + struct op *t1; + static int nesting = 0; + + for (i = 0; i < nesting; ++i) + shf_putc('\t', shf); + ++nesting; + shf_puts("{tree:" /*}*/, shf); + if (t == NULL) { + name = "(null)"; + goto out; + } + switch (t->type) { +#define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/ + + OPEN(TCOM) + if (t->vars) { + i = 0; + w = (const char **)t->vars; + while (*w) { + shf_putc('\n', shf); + for (int j = 0; j < nesting; ++j) + shf_putc('\t', shf); + shf_fprintf(shf, " var%d<", i++); + dumpwdvar(shf, *w++); + shf_putc('>', shf); + } + } else + shf_puts(" #no-vars#", shf); + if (t->args) { + i = 0; + w = t->args; + while (*w) { + shf_putc('\n', shf); + for (int j = 0; j < nesting; ++j) + shf_putc('\t', shf); + shf_fprintf(shf, " arg%d<", i++); + dumpwdvar(shf, *w++); + shf_putc('>', shf); + } + } else + shf_puts(" #no-args#", shf); + break; + OPEN(TEXEC) + dumpleftandout: + t = t->left; + dumpandout: + shf_putc('\n', shf); + dumptree(shf, t); + break; + OPEN(TPAREN) + goto dumpleftandout; + OPEN(TPIPE) + dumpleftmidrightandout: + shf_putc('\n', shf); + dumptree(shf, t->left); +/* middumprightandout: (unused) */ + shf_fprintf(shf, "/%s:", name); + dumprightandout: + t = t->right; + goto dumpandout; + OPEN(TLIST) + goto dumpleftmidrightandout; + OPEN(TOR) + goto dumpleftmidrightandout; + OPEN(TAND) + goto dumpleftmidrightandout; + OPEN(TBANG) + goto dumprightandout; + OPEN(TDBRACKET) + i = 0; + w = t->args; + while (*w) { + shf_putc('\n', shf); + for (int j = 0; j < nesting; ++j) + shf_putc('\t', shf); + shf_fprintf(shf, " arg%d<", i++); + dumpwdvar(shf, *w++); + shf_putc('>', shf); + } + break; + OPEN(TFOR) + dumpfor: + shf_fprintf(shf, " str<%s>", t->str); + if (t->vars != NULL) { + i = 0; + w = (const char **)t->vars; + while (*w) { + shf_putc('\n', shf); + for (int j = 0; j < nesting; ++j) + shf_putc('\t', shf); + shf_fprintf(shf, " var%d<", i++); + dumpwdvar(shf, *w++); + shf_putc('>', shf); + } + } + goto dumpleftandout; + OPEN(TSELECT) + goto dumpfor; + OPEN(TCASE) + shf_fprintf(shf, " str<%s>", t->str); + i = 0; + for (t1 = t->left; t1 != NULL; t1 = t1->right) { + shf_putc('\n', shf); + for (int j = 0; j < nesting; ++j) + shf_putc('\t', shf); + shf_fprintf(shf, " sub%d[(", i); + w = (const char **)t1->vars; + while (*w) { + dumpwdvar(shf, *w); + if (w[1] != NULL) + shf_putc('|', shf); + ++w; + } + shf_putc(')', shf); + shf_putc('\n', shf); + dumptree(shf, t1->left); + shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++); + } + break; + OPEN(TWHILE) + goto dumpleftmidrightandout; + OPEN(TUNTIL) + goto dumpleftmidrightandout; + OPEN(TBRACE) + goto dumpleftandout; + OPEN(TCOPROC) + goto dumpleftandout; + OPEN(TASYNC) + goto dumpleftandout; + OPEN(TFUNCT) + shf_fprintf(shf, " str<%s> ksh<%s>", t->str, + t->u.ksh_func ? "yes" : "no"); + goto dumpleftandout; + OPEN(TTIME) + goto dumpleftandout; + OPEN(TIF) + dumpif: + shf_putc('\n', shf); + dumptree(shf, t->left); + t = t->right; + if (t->left != NULL) { + shf_puts(" /TTHEN:\n", shf); + dumptree(shf, t->left); + } + if (t->right && t->right->type == TELIF) { + shf_puts(" /TELIF:", shf); + t = t->right; + goto dumpif; + } + if (t->right != NULL) { + shf_puts(" /TELSE:\n", shf); + dumptree(shf, t->right); + } + break; + OPEN(TEOF) + dumpunexpected: + shf_puts("unexpected", shf); + break; + OPEN(TELIF) + goto dumpunexpected; + OPEN(TPAT) + goto dumpunexpected; + default: + name = "TINVALID"; + shf_fprintf(shf, "{T<%d>:" /*}*/, t->type); + goto dumpunexpected; + +#undef OPEN + } + out: + shf_fprintf(shf, /*{*/ " /%s}\n", name); + --nesting; +} +#endif @@ -1,7 +1,7 @@ /* $OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser <tg@mirbsd.org> * * Provided that these terms and disclaimer and all copyright notices @@ -26,9 +26,9 @@ #include <sys/sysctl.h> #endif -__RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var.c,v 1.132 2011/09/07 15:24:22 tg Exp $"); -/* +/*- * Variables * * WARNING: unreadable code, needs a rewrite @@ -37,8 +37,11 @@ __RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $"); * otherwise, (val.s + type) contains string value. * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting. */ + static struct tbl vtemp; static struct table specials; +static uint32_t lcg_state = 5381; + static char *formatstr(struct tbl *, const char *); static void exportprep(struct tbl *, const char *); static int special(const char *); @@ -47,14 +50,7 @@ static void getspec(struct tbl *); static void setspec(struct tbl *); static void unsetspec(struct tbl *); static int getint(struct tbl *, mksh_ari_t *, bool); -static mksh_ari_t intval(struct tbl *); -static struct tbl *arraysearch(struct tbl *, uint32_t); static const char *array_index_calc(const char *, bool *, uint32_t *); -static uint32_t oaathash_update(register uint32_t, register const uint8_t *, - register size_t); -static uint32_t oaathash_finalise(register uint32_t); - -uint8_t set_refflag = 0; /* * create a new block for function calls and simple commands @@ -68,7 +64,8 @@ newblock(void) l = alloc(sizeof(struct block), ATEMP); l->flags = 0; - ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */ + /* TODO: could use e->area (l->area => l->areap) */ + ainit(&l->area); if (!e->loc) { l->argc = 0; l->argv = empty; @@ -77,8 +74,8 @@ newblock(void) l->argv = e->loc->argv; } l->exit = l->error = NULL; - ktinit(&l->vars, &l->area, 0); - ktinit(&l->funs, &l->area, 0); + ktinit(&l->area, &l->vars, 0); + ktinit(&l->area, &l->funs, 0); l->next = e->loc; e->loc = l; } @@ -89,12 +86,15 @@ newblock(void) void popblock(void) { + ssize_t i; struct block *l = e->loc; struct tbl *vp, **vpp = l->vars.tbls, *vq; - int i; - e->loc = l->next; /* pop block */ - for (i = l->vars.size; --i >= 0; ) + /* pop block */ + e->loc = l->next; + + i = 1 << (l->vars.tshift); + while (--i >= 0) if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) { if ((vq = global(vp->name))->flag & ISSET) setspec(vq); @@ -117,6 +117,7 @@ enum var_specs { V_MAX }; +/* this is biased with -1 relative to VARSPEC_ENUMS */ static const char * const initvar_names[] = { #define VARSPEC_ITEMS #include "var_spec.h" @@ -128,8 +129,9 @@ initvar(void) int i = 0; struct tbl *tp; - ktinit(&specials, APERM, - /* must be 80% of 2^n (currently 12 specials) */ 16); + ktinit(APERM, &specials, + /* currently 12 specials -> 80% of 16 (2^4) */ + 4); while (i < V_MAX - 1) { tp = ktenter(&specials, initvar_names[i], hash(initvar_names[i])); @@ -138,32 +140,49 @@ initvar(void) } } -/* Used to calculate an array index for global()/local(). Sets *arrayp to - * true if this is an array, sets *valp to the array index, returns +/* common code for several functions below */ +static struct block * +varsearch(struct block *l, struct tbl **vpp, const char *vn, uint32_t h) +{ + register struct tbl *vp; + + if (l) { + varsearch_loop: + if ((vp = ktsearch(&l->vars, vn, h)) != NULL) + goto varsearch_out; + if (l->next != NULL) { + l = l->next; + goto varsearch_loop; + } + } + vp = NULL; + varsearch_out: + *vpp = vp; + return (l); +} + +/* + * Used to calculate an array index for global()/local(). Sets *arrayp + * to true if this is an array, sets *valp to the array index, returns * the basename of the array. */ static const char * array_index_calc(const char *n, bool *arrayp, uint32_t *valp) { const char *p; - int len; + size_t len; char *ap = NULL; *arrayp = false; redo_from_ref: p = skip_varname(n, false); - if (!set_refflag && (p != n) && ksh_isalphx(n[0])) { - struct block *l = e->loc; + if (set_refflag == SRF_NOP && (p != n) && ksh_isalphx(n[0])) { struct tbl *vp; char *vn; - uint32_t h; strndupx(vn, n, p - n, ATEMP); - h = hash(vn); /* check if this is a reference */ - do { - vp = ktsearch(&l->vars, vn, h); - } while (!vp && (l = l->next)); + varsearch(e->loc, &vp, vn, hash(vn)); afree(vn, ATEMP); if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) == (DEFINED|ASSOC)) { @@ -181,7 +200,7 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp) char *sub, *tmp; mksh_ari_t rval; - /* Calculate the value of the subscript */ + /* calculate the value of the subscript */ *arrayp = true; strndupx(tmp, p + 1, len - 2, ATEMP); sub = substitute(tmp, 0); @@ -236,7 +255,7 @@ global(const char *n) vp->val.i = kshpid; break; case '!': - /* If no job, expand to nothing */ + /* if no job, expand to nothing */ if ((vp->val.i = j_async()) == 0) vp->flag &= ~(ISSET|INTEGER); break; @@ -255,17 +274,9 @@ global(const char *n) } return (vp); } - for (l = e->loc; ; l = l->next) { - vp = ktsearch(&l->vars, n, h); - if (vp != NULL) { - if (array) - return (arraysearch(vp, val)); - else - return (vp); - } - if (l->next == NULL) - break; - } + l = varsearch(e->loc, &vp, n, h); + if (vp != NULL) + return (array ? arraysearch(vp, val) : vp); vp = ktenter(&l->vars, n, h); if (array) vp = arraysearch(vp, val); @@ -286,7 +297,7 @@ local(const char *n, bool copy) bool array; uint32_t h, val; - /* Check to see if this is an array */ + /* check to see if this is an array */ n = array_index_calc(n, &array, &val); h = hash(n); if (!ksh_isalphx(*n)) { @@ -298,12 +309,10 @@ local(const char *n, bool copy) } vp = ktenter(&l->vars, n, h); if (copy && !(vp->flag & DEFINED)) { - struct block *ll = l; - struct tbl *vq = NULL; + struct tbl *vq; - while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h))) - ; - if (vq) { + varsearch(l->next, &vq, n, h); + if (vq != NULL) { vp->flag |= vq->flag & (EXPORT | INTEGER | RDONLY | LJUST | RJUST | ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L); @@ -329,18 +338,23 @@ str_val(struct tbl *vp) if ((vp->flag&SPECIAL)) getspec(vp); if (!(vp->flag&ISSET)) - s = null; /* special to dollar() */ - else if (!(vp->flag&INTEGER)) /* string source */ + /* special to dollar() */ + s = null; + else if (!(vp->flag&INTEGER)) + /* string source */ s = vp->val.s + vp->type; - else { /* integer source */ - /* worst case number length is when base=2 */ - /* 1 (minus) + 2 (base, up to 36) + 1 ('#') + number of bits - * in the mksh_uari_t + 1 (NUL) */ + else { + /* integer source */ + mksh_uari_t n; + int base; + /** + * worst case number length is when base == 2: + * 1 (minus) + 2 (base, up to 36) + 1 ('#') + + * number of bits in the mksh_uari_t + 1 (NUL) + */ char strbuf[1 + 2 + 1 + 8 * sizeof(mksh_uari_t) + 1]; const char *digits = (vp->flag & UCASEV_AL) ? digits_uc : digits_lc; - mksh_uari_t n; - int base; s = strbuf + sizeof(strbuf); if (vp->flag & INT_U) @@ -349,6 +363,8 @@ str_val(struct tbl *vp) n = (vp->val.i < 0) ? -vp->val.i : vp->val.i; base = (vp->type == 0) ? 10 : vp->type; + if (base == 1 && n == 0) + base = 2; if (base == 1) { size_t sz = 1; @@ -375,7 +391,8 @@ str_val(struct tbl *vp) if (!(vp->flag & INT_U) && vp->val.i < 0) *--s = '-'; } - if (vp->flag & (RJUST|LJUST)) /* case already dealt with */ + if (vp->flag & (RJUST|LJUST)) + /* case already dealt with */ s = formatstr(vp, s); else strdupx(s, s, ATEMP); @@ -383,20 +400,6 @@ str_val(struct tbl *vp) return (s); } -/* get variable integer value, with error checking */ -static mksh_ari_t -intval(struct tbl *vp) -{ - mksh_ari_t num; - int base; - - base = getint(vp, &num, false); - if (base == -1) - /* XXX check calls - is error here ok by POSIX? */ - errorf("%s: bad number", str_val(vp)); - return (num); -} - /* set variable to string value */ int setstr(struct tbl *vq, const char *s, int error_ok) @@ -406,12 +409,13 @@ setstr(struct tbl *vq, const char *s, int error_ok) error_ok &= ~0x4; if ((vq->flag & RDONLY) && !no_ro_check) { - warningf(true, "%s: is read only", vq->name); + warningf(true, "%s: %s", vq->name, "is read only"); if (!error_ok) - errorfz(); + errorfxz(2); return (0); } - if (!(vq->flag&INTEGER)) { /* string dest */ + if (!(vq->flag&INTEGER)) { + /* string dest */ if ((vq->flag&ALLOC)) { /* debugging */ if (s >= vq->val.s && @@ -431,7 +435,8 @@ setstr(struct tbl *vq, const char *s, int error_ok) strdupx(vq->val.s, s, vq->areap); vq->flag |= ALLOC; } - } else { /* integer dest */ + } else { + /* integer dest */ if (!v_evaluate(vq, s, error_ok, true)) return (0); } @@ -479,8 +484,6 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) return (vp->type); } s = vp->val.s + vp->type; - if (s == NULL) /* redundant given initial test */ - s = null; base = 10; num = 0; neg = 0; @@ -541,7 +544,8 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) return (base); } -/* convert variable vq to integer variable, setting its value from vp +/* + * convert variable vq to integer variable, setting its value from vp * (vq and vp may be the same) */ struct tbl * @@ -552,17 +556,26 @@ setint_v(struct tbl *vq, struct tbl *vp, bool arith) if ((base = getint(vp, &num, arith)) == -1) return (NULL); + setint_n(vq, num); + if (vq->type == 0) + /* default base */ + vq->type = base; + return (vq); +} + +/* convert variable vq to integer variable, setting its value to num */ +void +setint_n(struct tbl *vq, mksh_ari_t num) +{ if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) { vq->flag &= ~ALLOC; + vq->type = 0; afree(vq->val.s, vq->areap); } vq->val.i = num; - if (vq->type == 0) /* default base */ - vq->type = base; vq->flag |= ISSET|INTEGER; if (vq->flag&SPECIAL) setspec(vq); - return (vq); } static char * @@ -572,10 +585,11 @@ formatstr(struct tbl *vp, const char *s) char *p, *q; size_t psiz; - olen = utf_mbswidth(s); + olen = (int)utf_mbswidth(s); if (vp->flag & (RJUST|LJUST)) { - if (!vp->u2.field) /* default field width */ + if (!vp->u2.field) + /* default field width */ vp->u2.field = olen; nlen = vp->u2.field; } else @@ -649,33 +663,38 @@ exportprep(struct tbl *vp, const char *val) { char *xp; char *op = (vp->flag&ALLOC) ? vp->val.s : NULL; - int namelen = strlen(vp->name); - int vallen = strlen(val) + 1; + size_t namelen, vallen; + + namelen = strlen(vp->name); + vallen = strlen(val) + 1; vp->flag |= ALLOC; + /* since name+val are both in memory this can go unchecked */ xp = alloc(namelen + 1 + vallen, vp->areap); memcpy(vp->val.s = xp, vp->name, namelen); xp += namelen; *xp++ = '='; - vp->type = xp - vp->val.s; /* offset to value */ + /* offset to value */ + vp->type = xp - vp->val.s; memcpy(xp, val, vallen); if (op != NULL) afree(op, vp->areap); } /* - * lookup variable (according to (set&LOCAL)), - * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, - * LCASEV, UCASEV_AL), and optionally set its value if an assignment. + * lookup variable (according to (set&LOCAL)), set its attributes + * (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, LCASEV, + * UCASEV_AL), and optionally set its value if an assignment. */ struct tbl * -typeset(const char *var, Tflag set, Tflag clr, int field, int base) +typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) { struct tbl *vp; struct tbl *vpbase, *t; char *tvar; const char *val; - int len; + size_t len; + bool vappend = false; /* check for valid variable name, search for value */ val = skip_varname(var, false); @@ -684,55 +703,80 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) mkssert(var != NULL); mkssert(*var != 0); if (*val == '[') { - if (set_refflag) - errorf("%s: reference variable cannot be an array", - var); + if (set_refflag != SRF_NOP) + errorf("%s: %s", var, + "reference variable can't be an array"); len = array_ref_len(val); if (len == 0) return (NULL); - /* IMPORT is only used when the shell starts up and is + /* + * IMPORT is only used when the shell starts up and is * setting up its environment. Allow only simple array - * references at this time since parameter/command substitution - * is preformed on the [expression] which would be a major - * security hole. + * references at this time since parameter/command + * substitution is preformed on the [expression] which + * would be a major security hole. */ if (set & IMPORT) { - int i; + size_t i; + for (i = 1; i < len - 1; i++) if (!ksh_isdigit(val[i])) return (NULL); } val += len; } - if (*val == '=') - strndupx(tvar, var, val++ - var, ATEMP); - else { - /* Importing from original environment: must have an = */ + if (val[0] == '=' || (val[0] == '+' && val[1] == '=')) { + strndupx(tvar, var, val - var, ATEMP); + if (*val++ == '+') { + ++val; + vappend = true; + } + } else { + /* importing from original environment: must have an = */ if (set & IMPORT) return (NULL); strdupx(tvar, var, ATEMP); val = NULL; - /* handle foo[*] ⇒ foo (whole array) mapping for R39b */ + /* handle foo[*] => foo (whole array) mapping for R39b */ len = strlen(tvar); - if (len > 3 && tvar[len-3] == '[' && tvar[len-2] == '*' && - tvar[len-1] == ']') - tvar[len-3] = '\0'; + if (len > 3 && tvar[len - 3] == '[' && tvar[len - 2] == '*' && + tvar[len - 1] == ']') + tvar[len - 3] = '\0'; } - /* Prevent typeset from creating a local PATH/ENV/SHELL */ + if (set_refflag == SRF_ENABLE) { + const char *qval; + + /* bail out on 'nameref foo+=bar' */ + if (vappend) + errorfz(); + /* find value if variable already exists */ + if ((qval = val) == NULL) { + varsearch(e->loc, &vp, tvar, hash(tvar)); + if (vp != NULL) + qval = str_val(vp); + } + /* silently ignore 'nameref foo=foo' */ + if (qval != NULL && !strcmp(qval, tvar)) { + afree(tvar, ATEMP); + return (&vtemp); + } + } + + /* prevent typeset from creating a local PATH/ENV/SHELL */ if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 || strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0)) - errorf("%s: restricted", tvar); + errorf("%s: %s", tvar, "restricted"); - vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) : + vp = (set&LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) : global(tvar); - if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC) + if (set_refflag == SRF_DISABLE && (vp->flag & (ARRAY|ASSOC)) == ASSOC) vp->flag &= ~ASSOC; - else if (set_refflag == 1) { + else if (set_refflag == SRF_ENABLE) { if (vp->flag & ARRAY) { struct tbl *a, *tmp; - /* Free up entire array */ + /* free up entire array */ for (a = vp->u.array; a; ) { tmp = a; a = a->u.array; @@ -750,21 +794,24 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp; - /* only allow export flag to be set. AT&T ksh allows any attribute to - * be changed which means it can be truncated or modified (-L/-R/-Z/-i) + /* + * only allow export flag to be set; AT&T ksh allows any + * attribute to be changed which means it can be truncated or + * modified (-L/-R/-Z/-i) */ if ((vpbase->flag&RDONLY) && (val || clr || (set & ~EXPORT))) /* XXX check calls - is error here ok by POSIX? */ - errorf("%s: is read only", tvar); + errorfx(2, "%s: %s", tvar, "is read only"); afree(tvar, ATEMP); /* most calls are with set/clr == 0 */ if (set | clr) { bool ok = true; - /* XXX if x[0] isn't set, there will be problems: need to have - * one copy of attributes for arrays... + /* + * XXX if x[0] isn't set, there will be problems: need + * to have one copy of attributes for arrays... */ for (t = vpbase; t; t = t->u.array) { bool fake_assign; @@ -791,8 +838,9 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) t->flag &= ~ALLOC; } t->flag = (t->flag | set) & ~clr; - /* Don't change base if assignment is to be done, - * in case assignment fails. + /* + * Don't change base if assignment is to be + * done, in case assignment fails. */ if ((set & INTEGER) && base > 0 && (!val || t != vp)) t->type = base; @@ -800,9 +848,11 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) t->u2.field = field; if (fake_assign) { if (!setstr(t, s, KSH_RETURN_ERROR)) { - /* Somewhat arbitrary action here: - * zap contents of variable, but keep - * the flag settings. + /* + * Somewhat arbitrary action + * here: zap contents of + * variable, but keep the flag + * settings. */ ok = false; if (t->flag & INTEGER) @@ -823,15 +873,26 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) } if (val != NULL) { + char *tval; + + if (vappend) { + tval = shf_smprintf("%s%s", str_val(vp), val); + val = tval; + } else + tval = NULL; + if (vp->flag&INTEGER) { /* do not zero base before assignment */ setstr(vp, val, KSH_UNWIND_ERROR | 0x4); - /* Done after assignment to override default */ + /* done after assignment to override default */ if (base > 0) vp->type = base; } else /* setstr can't fail (readonly check already done) */ setstr(vp, val, KSH_RETURN_ERROR | 0x4); + + if (tval != NULL) + afree(tval, ATEMP); } /* only x[0] is ever exported, so use vpbase */ @@ -855,7 +916,7 @@ unset(struct tbl *vp, int flags) if ((vp->flag & ARRAY) && (flags & 1)) { struct tbl *a, *tmp; - /* Free up entire array */ + /* free up entire array */ for (a = vp->u.array; a; ) { tmp = a; a = a->u.array; @@ -869,20 +930,22 @@ unset(struct tbl *vp, int flags) vp->flag &= ~(ALLOC|ISSET); return; } - /* If foo[0] is being unset, the remainder of the array is kept... */ + /* if foo[0] is being unset, the remainder of the array is kept... */ vp->flag &= SPECIAL | ((flags & 1) ? 0 : ARRAY|DEFINED); if (vp->flag & SPECIAL) - unsetspec(vp); /* responsible for 'unspecial'ing var */ + /* responsible for 'unspecial'ing var */ + unsetspec(vp); } -/* return a pointer to the first char past a legal variable name (returns the - * argument if there is no legal name, returns a pointer to the terminating - * NUL if whole string is legal). +/* + * Return a pointer to the first char past a legal variable name + * (returns the argument if there is no legal name, returns a pointer to + * the terminating NUL if whole string is legal). */ const char * skip_varname(const char *s, int aok) { - int alen; + size_t alen; if (s && ksh_isalphx(*s)) { while (*++s && ksh_isalnux(*s)) @@ -896,7 +959,8 @@ skip_varname(const char *s, int aok) /* Return a pointer to the first character past any legal variable name */ const char * skip_wdvarname(const char *s, - int aok) /* skip array de-reference? */ + /* skip array de-reference? */ + bool aok) { if (s[0] == CHAR && ksh_isalphx(s[1])) { do { @@ -908,7 +972,7 @@ skip_wdvarname(const char *s, char c; int depth = 0; - while (1) { + while (/* CONSTCOND */ 1) { if (p[0] != CHAR) break; c = p[1]; @@ -927,7 +991,7 @@ skip_wdvarname(const char *s, /* Check if coded string s is a variable name */ int -is_wdvarname(const char *s, int aok) +is_wdvarname(const char *s, bool aok) { const char *p = skip_wdvarname(s, aok); @@ -940,7 +1004,8 @@ is_wdvarassign(const char *s) { const char *p = skip_wdvarname(s, true); - return (p != s && p[0] == CHAR && p[1] == '='); + return (p != s && p[0] == CHAR && + (p[1] == '=' || (p[1] == '+' && p[2] == CHAR && p[3] == '='))); } /* @@ -949,14 +1014,16 @@ is_wdvarassign(const char *s) char ** makenv(void) { + ssize_t i; struct block *l; XPtrV denv; struct tbl *vp, **vpp; - int i; XPinit(denv, 64); - for (l = e->loc; l != NULL; l = l->next) - for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; ) + for (l = e->loc; l != NULL; l = l->next) { + vpp = l->vars.tbls; + i = 1 << (l->vars.tshift); + while (--i >= 0) if ((vp = *vpp++) != NULL && (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) { struct block *l2; @@ -979,90 +1046,11 @@ makenv(void) } XPput(denv, vp->val.s); } + } XPput(denv, NULL); return ((char **)XPclose(denv)); } -/* Bob Jenkins' one-at-a-time hash */ -static uint32_t -oaathash_update(register uint32_t h, register const uint8_t *cp, - register size_t n) -{ - while (n--) { - h += *cp++; - h += h << 10; - h ^= h >> 6; - } - - return (h); -} - -static uint32_t -oaathash_finalise(register uint32_t h) -{ - h += h << 3; - h ^= h >> 11; - h += h << 15; - - return (h); -} - -uint32_t -oaathash_full(register const uint8_t *bp) -{ - register uint32_t h = 0; - register uint8_t c; - - while ((c = *bp++)) { - h += c; - h += h << 10; - h ^= h >> 6; - } - - return (oaathash_finalise(h)); -} - -void -change_random(const void *vp, size_t n) -{ - register uint32_t h = 0x100; -#if defined(__OpenBSD__) - int mib[2]; - uint8_t k[3]; - size_t klen; -#endif - - kshstate_v.cr_dp = vp; - kshstate_v.cr_dsz = n; - gettimeofday(&kshstate_v.cr_tv, NULL); - h = oaathash_update(oaathash_update(h, (void *)&kshstate_v, - sizeof(kshstate_v)), vp, n); - kshstate_v.lcg_state_ = oaathash_finalise(h); - -#if defined(__OpenBSD__) - /* OpenBSD, MirBSD: proper kernel entropy comes at zero cost */ - - mib[0] = CTL_KERN; - mib[1] = KERN_ARND; - klen = sizeof(k); - sysctl(mib, 2, k, &klen, &kshstate_v.lcg_state_, - sizeof(kshstate_v.lcg_state_)); - /* we ignore failures and take in k anyway */ - h = oaathash_update(h, k, sizeof(k)); - kshstate_v.lcg_state_ = oaathash_finalise(h); -#elif defined(MKSH_A4PB) - /* forced by the user to use arc4random_pushb(3) • Cygwin? */ - { - uint32_t prv; - - prv = arc4random_pushb(&kshstate_v.lcg_state_, - sizeof(kshstate_v.lcg_state_)); - h = oaathash_update(h, &prv, sizeof(prv)); - } - kshstate_v.lcg_state_ = oaathash_finalise(h); -#endif -} - /* * handle special variables with side effects - PATH, SECONDS. */ @@ -1117,8 +1105,7 @@ getspec(struct tbl *vp) * this is the same Linear Congruential PRNG as Borland * C/C++ allegedly uses in its built-in rand() function */ - i = ((kshstate_v.lcg_state_ = - 22695477 * kshstate_v.lcg_state_ + 1) >> 16) & 0x7FFF; + i = ((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF; break; case V_HISTSIZE: i = histsize; @@ -1146,7 +1133,7 @@ getspec(struct tbl *vp) return; } vp->flag &= ~SPECIAL; - setint(vp, i); + setint_n(vp, i); vp->flag |= SPECIAL; } @@ -1163,7 +1150,8 @@ setspec(struct tbl *vp) afree(path, APERM); s = str_val(vp); strdupx(path, s, APERM); - flushcom(1); /* clear tracked aliases */ + /* clear tracked aliases */ + flushcom(true); return; case V_IFS: setctypes(s = str_val(vp), C_IFS); @@ -1174,28 +1162,25 @@ setspec(struct tbl *vp) afree(tmpdir, APERM); tmpdir = NULL; } - /* Use tmpdir iff it is an absolute path, is writable and - * searchable and is a directory... + /* + * Use tmpdir iff it is an absolute path, is writable + * and searchable and is a directory... */ { struct stat statb; s = str_val(vp); + /* LINTED use of access */ if (s[0] == '/' && access(s, W_OK|X_OK) == 0 && stat(s, &statb) == 0 && S_ISDIR(statb.st_mode)) strdupx(tmpdir, s, APERM); } - break; + return; #if HAVE_PERSISTENT_HISTORY case V_HISTFILE: sethistfile(str_val(vp)); - break; + return; #endif - case V_TMOUT: - /* AT&T ksh seems to do this (only listen if integer) */ - if (vp->flag & INTEGER) - ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0; - break; /* common sub-cases */ case V_OPTIND: @@ -1205,8 +1190,14 @@ setspec(struct tbl *vp) case V_RANDOM: case V_SECONDS: case V_LINENO: + case V_TMOUT: vp->flag &= ~SPECIAL; - i = intval(vp); + if (getint(vp, &i, false) == -1) { + s = str_val(vp); + if (st != V_RANDOM) + errorf("%s: %s: %s", vp->name, "bad number", s); + i = hash(s); + } vp->flag |= SPECIAL; break; default: @@ -1236,7 +1227,7 @@ setspec(struct tbl *vp) * mksh R39d+ no longer has the traditional repeatability * of $RANDOM sequences, but always retains state */ - change_random(&i, sizeof(i)); + rndset((long)i); break; case V_SECONDS: { @@ -1250,6 +1241,9 @@ setspec(struct tbl *vp) /* The -1 is because line numbering starts at 1. */ user_lineno = (unsigned int)i - current_lineno - 1; break; + case V_TMOUT: + ksh_tmout = i >= 0 ? i : 0; + break; } } @@ -1261,7 +1255,8 @@ unsetspec(struct tbl *vp) if (path) afree(path, APERM); strdupx(path, def_path, APERM); - flushcom(1); /* clear tracked aliases */ + /* clear tracked aliases */ + flushcom(true); break; case V_IFS: setctypes(" \t\n", C_IFS); @@ -1277,7 +1272,8 @@ unsetspec(struct tbl *vp) case V_LINENO: case V_RANDOM: case V_SECONDS: - case V_TMOUT: /* AT&T ksh leaves previous value in place */ + case V_TMOUT: + /* AT&T ksh leaves previous value in place */ unspecial(vp->name); break; @@ -1295,14 +1291,14 @@ unsetspec(struct tbl *vp) * Search for (and possibly create) a table entry starting with * vp, indexed by val. */ -static struct tbl * +struct tbl * arraysearch(struct tbl *vp, uint32_t val) { struct tbl *prev, *curr, *news; size_t len; vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC; - /* The table entry is always [0] */ + /* the table entry is always [0] */ if (val == 0) return (vp); prev = vp; @@ -1317,9 +1313,10 @@ arraysearch(struct tbl *vp, uint32_t val) news = curr; } else news = NULL; - len = strlen(vp->name) + 1; if (!news) { - news = alloc(offsetof(struct tbl, name[0]) + len, vp->areap); + len = strlen(vp->name); + checkoktoadd(len, 1 + offsetof(struct tbl, name[0])); + news = alloc(offsetof(struct tbl, name[0]) + ++len, vp->areap); memcpy(news->name, vp->name, len); } news->flag = (vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL)) | AINDEX; @@ -1328,22 +1325,24 @@ arraysearch(struct tbl *vp, uint32_t val) news->u2.field = vp->u2.field; news->ua.index = val; - if (curr != news) { /* not reusing old array entry */ + if (curr != news) { + /* not reusing old array entry */ prev->u.array = news; news->u.array = curr; } return (news); } -/* Return the length of an array reference (eg, [1+2]) - cp is assumed - * to point to the open bracket. Returns 0 if there is no matching closing - * bracket. +/* + * Return the length of an array reference (eg, [1+2]) - cp is assumed + * to point to the open bracket. Returns 0 if there is no matching + * closing bracket. */ -int +size_t array_ref_len(const char *cp) { const char *s = cp; - int c; + char c; int depth = 0; while ((c = *s++) && (c != ']' || --depth)) @@ -1377,32 +1376,50 @@ mksh_uari_t set_array(const char *var, bool reset, const char **vals) { struct tbl *vp, *vq; - mksh_uari_t i; + mksh_uari_t i = 0, j = 0; const char *ccp; #ifndef MKSH_SMALL - char *cp; - mksh_uari_t j; + char *cp = NULL; + size_t n; #endif /* to get local array, use "typeset foo; set -A foo" */ - vp = global(var); +#ifndef MKSH_SMALL + n = strlen(var); + if (n > 0 && var[n - 1] == '+') { + /* append mode */ + reset = false; + strndupx(cp, var, n - 1, ATEMP); + } +#define CPORVAR (cp ? cp : var) +#else +#define CPORVAR var +#endif + vp = global(CPORVAR); /* Note: AT&T ksh allows set -A but not set +A of a read-only var */ if ((vp->flag&RDONLY)) - errorf("%s: is read only", var); + errorfx(2, "%s: %s", CPORVAR, "is read only"); /* This code is quite non-optimal */ if (reset) /* trash existing values and attributes */ unset(vp, 1); - /* todo: would be nice for assignment to completely succeed or + /* + * TODO: would be nice for assignment to completely succeed or * completely fail. Only really effects integer arrays: * evaluation of some of vals[] may fail... */ - i = 0; #ifndef MKSH_SMALL - j = 0; -#else -#define j i + if (cp != NULL) { + /* find out where to set when appending */ + for (vq = vp; vq; vq = vq->u.array) { + if (!(vq->flag & ISSET)) + continue; + if (arrayindex(vq) >= j) + j = arrayindex(vq) + 1; + } + afree(cp, ATEMP); + } #endif while ((ccp = vals[i])) { #ifndef MKSH_SMALL @@ -1432,9 +1449,7 @@ set_array(const char *var, bool reset, const char **vals) /* would be nice to deal with errors here... (see above) */ setstr(vq, ccp, KSH_RETURN_ERROR); i++; -#ifndef MKSH_SMALL j++; -#endif } return (i); @@ -1448,7 +1463,7 @@ change_winsz(void) #ifdef TIOCGWINSZ if (tty_fd < 0) /* non-FTALKING, try to get an fd anyway */ - tty_init(false, false); + tty_init(true, false); #endif x_cols = -1; } @@ -1479,12 +1494,42 @@ change_winsz(void) } uint32_t -evilhash(const char *s) +hash(const void *s) { - register uint32_t h = 0x100; + register uint32_t h; + + NZATInit(h); + NZATUpdateString(h, s); + NZATFinish(h); + return (h); +} + +void +rndset(long v) +{ + register uint32_t h; + + NZATInit(h); + NZATUpdateMem(h, &lcg_state, sizeof(lcg_state)); + NZATUpdateMem(h, &v, sizeof(v)); + +#if defined(arc4random_pushb_fast) || defined(MKSH_A4PB) + /* + * either we have very chap entropy get and push available, + * with malloc() pulling in this code already anyway, or the + * user requested us to use the old functions + */ + lcg_state = h; + NZAATFinish(lcg_state); +#if defined(arc4random_pushb_fast) + arc4random_pushb_fast(&lcg_state, sizeof(lcg_state)); + lcg_state = arc4random(); +#else + lcg_state = arc4random_pushb(&lcg_state, sizeof(lcg_state)); +#endif + NZATUpdateMem(h, &lcg_state, sizeof(lcg_state)); +#endif - h = oaathash_update(h, (void *)&kshstate_f, sizeof(kshstate_f)); - kshstate_f.h = oaathash_full((const uint8_t *)s); - return (oaathash_finalise(oaathash_update(h, - (void *)&kshstate_f.h, sizeof(kshstate_f.h)))); + NZAATFinish(h); + lcg_state = h; } diff --git a/src/var_spec.h b/src/var_spec.h index 4035cc9..b3bef4b 100644 --- a/src/var_spec.h +++ b/src/var_spec.h @@ -1,5 +1,5 @@ #if defined(VARSPEC_DEFNS) -__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.2 2011/06/05 19:58:21 tg Exp $"); #define FN(name) /* nothing */ #elif defined(VARSPEC_ENUMS) #define FN(name) V_##name, @@ -13,6 +13,8 @@ __RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $"); #define F0 FN #endif +/* NOTE: F0 are skipped for the ITEMS array, only FN generate names */ + /* 0 is always V_NONE */ F0(NONE) |
