From c2dc5def5e2273bb1d78b4ba032a3903dd0f980c Mon Sep 17 00:00:00 2001 From: Thorsten Glaser Date: Mon, 18 Feb 2013 23:02:51 +0000 Subject: [PATCH] Update mksh to R43 (formal release, from tarball) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The files in src/ are just the unmodified mksh release .tgz and is Not a Contribution to Android, it’s the upstream code project. ChangeLog: https://www.mirbsd.org/mksh.htm#clog Changes, other than upstream mksh code: • update Copyright years • sync with moving to a different AOSP project and other AOSP changes • mkmf.sh: add test compilation mode • be conservative in filedescriptor usage • don’t compile the $MirOS$ RCS IDs into the binary to save space • enable UTF-8 mode, to match reality • help memory leak debugging by adding -DDEBUG_LEAKS • update rest of mkmf.sh to work with updated AOSP and mksh • use ro.product.device property as default hostname in mkshrc (originally submitted by John Michelau ) • do not export PS1 in mkshrc (cross-shell developers agreed on this) • add “more” function to mkshrc to get a simplistic pager • add “hd” and “setenv” functions to mkshrc for better UX • mention Launchpad as upstream bugtracker as comment in mkshrc • change TMPDIR to /data/local/tmp for now, as /sqlite_stmt_journals is gone (although this is still no full replacement, it’s better than before) • address the segfaults seen by DONG-DONG YANG Change-Id: I2d4d175bc5163b3d6f5098024f98f316fe812e55 --- Android.mk | 60 +- NOTICE | 3 +- mkmf.sh | 179 ++-- mkshrc | 37 +- src/00-NOTE.txt | 22 - src/Build.sh | 1189 ++++++++++++++++++++------ src/Makefile | 97 --- src/check.pl | 29 +- src/check.t | 2115 ++++++++++++++++++++++++++++++++++++++++------- src/dot.mkshrc | 66 +- src/edit.c | 594 +++++++------ src/eval.c | 210 +++-- src/exec.c | 178 ++-- src/expr.c | 234 +++--- src/funcs.c | 802 +++++++++++------- src/histrap.c | 894 ++++++++++---------- src/jobs.c | 162 +++- src/lex.c | 269 +++--- src/main.c | 649 ++++++++++----- src/misc.c | 366 ++++---- src/mksh.1 | 237 ++++-- src/sh.h | 580 ++++++++----- src/sh_flags.h | 8 +- src/shf.c | 133 ++- src/syn.c | 315 ++++--- src/tree.c | 208 +++-- src/var.c | 307 +++---- src/var_spec.h | 4 +- 28 files changed, 6701 insertions(+), 3246 deletions(-) delete mode 100644 src/00-NOTE.txt delete mode 100644 src/Makefile diff --git a/Android.mk b/Android.mk index e53b863..7855926 100644 --- a/Android.mk +++ b/Android.mk @@ -1,4 +1,4 @@ -# Copyright © 2010 +# Copyright © 2010, 2013 # Thorsten Glaser # This file is provided under the same terms as mksh. @@ -34,31 +34,37 @@ LOCAL_SYSTEM_SHARED_LIBRARIES:= libc LOCAL_C_INCLUDES:= $(LOCAL_PATH)/src # additional flags first, then from Makefrag.inc: CFLAGS, CPPFLAGS -LOCAL_CFLAGS:= -DMKSH_DEFAULT_EXECSHELL=\"/system/bin/sh\" \ - -DMKSH_DEFAULT_TMPDIR=\"/sqlite_stmt_journals\" \ - -DMKSHRC_PATH=\"/system/etc/mkshrc\" \ - -fwrapv \ - -DMKSH_ASSUME_UTF8=0 -DMKSH_NOPWNAM \ - -D_GNU_SOURCE \ - -DHAVE_ATTRIBUTE_BOUNDED=0 -DHAVE_ATTRIBUTE_FORMAT=1 \ - -DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \ - -DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \ - -DHAVE_SYS_PARAM_H=1 -DHAVE_SYS_MKDEV_H=0 \ - -DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_SYSMACROS_H=1 \ - -DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 -DHAVE_LIBUTIL_H=0 \ - -DHAVE_PATHS_H=1 -DHAVE_STDBOOL_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=0 \ - -DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \ - -DHAVE_SETMODE=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=0 +LOCAL_CFLAGS:= -DMKSHRC_PATH=\"/system/etc/mkshrc\" \ + -DMKSH_DEFAULT_EXECSHELL=\"/system/bin/sh\" \ + -DMKSH_DEFAULT_TMPDIR=\"/data/local/tmp\" \ + -fno-asynchronous-unwind-tables -fwrapv \ + -DDEBUG_LEAKS -DMKSH_ASSUME_UTF8 -DMKSH_CONSERVATIVE_FDS \ + -DMKSH_DONT_EMIT_IDSTRING -DMKSH_NOPWNAM -DMKSH_BUILDSH \ + -D_GNU_SOURCE -DSETUID_CAN_FAIL_WITH_EAGAIN \ + -DHAVE_ATTRIBUTE_BOUNDED=0 -DHAVE_ATTRIBUTE_FORMAT=1 \ + -DHAVE_ATTRIBUTE_NORETURN=1 -DHAVE_ATTRIBUTE_UNUSED=1 \ + -DHAVE_ATTRIBUTE_USED=1 -DHAVE_SYS_TIME_H=1 -DHAVE_TIME_H=1 \ + -DHAVE_BOTH_TIME_H=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_RESOURCE_H=1 \ + -DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SYSMACROS_H=1 \ + -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_TERMIOS_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_ERRLIST=0 -DHAVE_SYS_SIGNAME=1 \ + -DHAVE_SYS_SIGLIST=1 -DHAVE_FLOCK=1 -DHAVE_LOCK_FCNTL=1 \ + -DHAVE_GETRUSAGE=1 -DHAVE_GETTIMEOFDAY=1 -DHAVE_KILLPG=1 \ + -DHAVE_MEMMOVE=1 -DHAVE_MKNOD=0 -DHAVE_MMAP=1 -DHAVE_NICE=1 \ + -DHAVE_REVOKE=0 -DHAVE_SETLOCALE_CTYPE=0 \ + -DHAVE_LANGINFO_CODESET=0 -DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 \ + -DHAVE_SETGROUPS=1 -DHAVE_STRERROR=1 -DHAVE_STRSIGNAL=0 \ + -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 -DHAVE_REVOKE_DECL=1 \ + -DHAVE_SYS_ERRLIST_DECL=0 -DHAVE_SYS_SIGLIST_DECL=1 \ + -DHAVE_PERSISTENT_HISTORY=0 -DHAVE_SILENT_IDIVWRAPV=0 \ + -DMKSH_BUILD_R=431 + +# check categories: shell:legacy-no int:32 android convfds no-histfile include $(BUILD_EXECUTABLE) diff --git a/NOTICE b/NOTICE index 350061f..2c542fc 100644 --- a/NOTICE +++ b/NOTICE @@ -1,7 +1,8 @@ mksh is covered by The MirOS Licence: /*- - * Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices diff --git a/mkmf.sh b/mkmf.sh index 0372d62..84e9845 100644 --- a/mkmf.sh +++ b/mkmf.sh @@ -1,5 +1,5 @@ -# Copyright © 2010 -# Thorsten Glaser +# Copyright © 2010, 2012, 2013 +# Thorsten Glaser # This file is provided under the same terms as mksh. #- # Helper script to let src/Build.sh generate Makefrag.inc @@ -8,11 +8,21 @@ # This script is supposed to be run from/inside AOSP by the # porter of mksh to Android (and only manually). +if test x"$1" = x"-t"; then + # test compilation + args=-r + mkmfmode=1 +else + # prepare for AOSP + args=-M + mkmfmode=0 +fi + cd "$(dirname "$0")" srcdir=$(pwd) rm -rf tmp mkdir tmp -cd ../../.. +cd ../.. aospdir=$(pwd) cd $srcdir/tmp @@ -42,64 +52,124 @@ LIBS= # Since we no longer use the NDK, the AOSP has to have been # built before using this script (targetting generic/emulator). -CC=$aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-gcc -addvar CPPFLAGS -I$aospdir/system/core/include \ - -I$aospdir/hardware/libhardware/include \ - -I$aospdir/system/core/include \ - -I$aospdir/hardware/libhardware/include \ - -I$aospdir/hardware/libhardware_legacy/include \ - -I$aospdir/hardware/ril/include \ - -I$aospdir/libnativehelper/include \ - -I$aospdir/frameworks/base/include \ - -I$aospdir/frameworks/base/opengl/include \ - -I$aospdir/external/skia/include \ - -I$aospdir/out/target/product/generic/obj/include \ - -I$aospdir/bionic/libc/arch-arm/include \ - -I$aospdir/bionic/libc/include \ - -I$aospdir/bionic/libstdc++/include \ - -I$aospdir/bionic/libc/kernel/common \ - -I$aospdir/bionic/libc/kernel/arch-arm \ - -I$aospdir/bionic/libm/include \ - -I$aospdir/bionic/libm/include/arch/arm \ - -I$aospdir/bionic/libthread_db/include \ - -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ \ - -I$aospdir/system/core/include/arch/linux-arm/ \ - -include $aospdir/system/core/include/arch/linux-arm/AndroidConfig.h \ +CC=$aospdir/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gcc +addvar CPPFLAGS \ + -I$aospdir/libnativehelper/include/nativehelper \ + -isystem $aospdir/system/core/include \ + -isystem $aospdir/hardware/libhardware/include \ + -isystem $aospdir/hardware/libhardware_legacy/include \ + -isystem $aospdir/hardware/ril/include \ + -isystem $aospdir/libnativehelper/include \ + -isystem $aospdir/frameworks/native/include \ + -isystem $aospdir/frameworks/native/opengl/include \ + -isystem $aospdir/frameworks/av/include \ + -isystem $aospdir/frameworks/base/include \ + -isystem $aospdir/frameworks/base/opengl/include \ + -isystem $aospdir/external/skia/include \ + -isystem $aospdir/out/target/product/generic/obj/include \ + -isystem $aospdir/bionic/libc/arch-arm/include \ + -isystem $aospdir/bionic/libc/include \ + -isystem $aospdir/bionic/libstdc++/include \ + -isystem $aospdir/bionic/libc/kernel/common \ + -isystem $aospdir/bionic/libc/kernel/arch-arm \ + -isystem $aospdir/bionic/libm/include \ + -isystem $aospdir/bionic/libm/include/arm \ + -isystem $aospdir/bionic/libthread_db/include \ + -D_FORTIFY_SOURCE=1 \ + -include $aospdir/build/core/combo/include/arch/linux-arm/AndroidConfig.h \ + -I$aospdir/build/core/combo/include/arch/linux-arm/ \ -DANDROID -DNDEBUG -UDEBUG -addvar CFLAGS -fno-exceptions -Wno-multichar -msoft-float -fpic \ - -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums \ - -march=armv5te -mtune=xscale -mthumb-interwork -fmessage-length=0 \ - -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Werror=return-type \ - -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point \ - -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once \ - -fgcse-after-reload -frerun-cse-after-loop -frename-registers -mthumb \ - -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -addvar LDFLAGS -nostdlib -Bdynamic -Wl,-T,$aospdir/build/core/armelf.x \ - -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections \ - -Wl,-z,nocopyreloc -Wl,--no-undefined \ +# who would have thought the AOSP devs are funny? -fno-builtin-sin +addvar CFLAGS \ + -fno-exceptions \ + -Wno-multichar \ + -msoft-float \ + -fpic \ + -fPIE \ + -ffunction-sections \ + -fdata-sections \ + -funwind-tables \ + -fstack-protector \ + -Wa,--noexecstack \ + -Werror=format-security \ + -fno-short-enums \ + -march=armv7-a \ + -mfloat-abi=softfp \ + -mfpu=vfpv3-d16 \ + -Wno-unused-but-set-variable \ + -fno-builtin-sin \ + -fno-strict-volatile-bitfields \ + -Wno-psabi \ + -mthumb-interwork \ + -fmessage-length=0 \ + -W \ + -Wall \ + -Wno-unused \ + -Winit-self \ + -Wpointer-arith \ + -Werror=return-type \ + -Werror=non-virtual-dtor \ + -Werror=address \ + -Werror=sequence-point \ + -g \ + -Wstrict-aliasing=2 \ + -fgcse-after-reload \ + -frerun-cse-after-loop \ + -frename-registers \ + -mthumb \ + -Os \ + -fomit-frame-pointer \ + -fno-strict-aliasing +addvar LDFLAGS \ + -nostdlib \ + -Bdynamic \ + -pie \ + -Wl,-dynamic-linker,/system/bin/linker \ + -Wl,--gc-sections \ + -Wl,-z,nocopyreloc \ + -Wl,-z,noexecstack \ + -Wl,-z,relro \ + -Wl,-z,now \ + -Wl,--warn-shared-textrel \ + -Wl,--icf=safe \ + -Wl,--fix-cortex-a8 \ + -Wl,--no-undefined \ $aospdir/out/target/product/generic/obj/lib/crtbegin_dynamic.o -addvar LIBS -L$aospdir/out/target/product/generic/obj/lib \ - -Wl,-rpath-link=$aospdir/out/target/product/generic/obj/lib -lc \ - $aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/../lib/gcc/arm-eabi/4.4.0/interwork/libgcc.a \ +addvar LIBS \ + -L$aospdir/out/target/product/generic/obj/lib \ + -Wl,-rpath-link=$aospdir/out/target/product/generic/obj/lib \ + -lc \ + -Wl,--no-whole-archive \ + $aospdir/out/target/product/generic/obj/STATIC_LIBRARIES/libcompiler-rt-extras_intermediates/libcompiler-rt-extras.a \ + $aospdir/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/../lib/gcc/arm-linux-androideabi/4.7/armv7-a/libgcc.a \ $aospdir/out/target/product/generic/obj/lib/crtend_android.o +### Flags used by test builds +if test $mkmfmode = 1; then + addvar CPPFLAGS '-DMKSHRC_PATH=\"/system/etc/mkshrc\"' + addvar CPPFLAGS '-DMKSH_DEFAULT_EXECSHELL=\"/system/bin/sh\"' + addvar CPPFLAGS '-DMKSH_DEFAULT_TMPDIR=\"/data/local/tmp\"' +fi + ### Override flags -# We don’t even *support* UTF-8 by default ☹ -addvar CPPFLAGS -DMKSH_ASSUME_UTF8=0 +# Let the shell free all memory upon exiting +addvar CPPFLAGS -DDEBUG_LEAKS +# UTF-8 works nowadays +addvar CPPFLAGS -DMKSH_ASSUME_UTF8 +# Reduce filedescriptor usage +addvar CPPFLAGS -DMKSH_CONSERVATIVE_FDS +# Leave out RCS ID strings from the binary +addvar CPPFLAGS -DMKSH_DONT_EMIT_IDSTRING # No getpwnam() calls (affects "cd ~username/" only) addvar CPPFLAGS -DMKSH_NOPWNAM -# Compile an extra small mksh (optional) -#addvar CPPFLAGS -DMKSH_SMALL # Leave out the ulimit builtin #addvar CPPFLAGS -DMKSH_NO_LIMITS +# Compile an extra small mksh (optional) +#addvar CPPFLAGS -DMKSH_SMALL # Set target platform -TARGET_OS=Linux -# Building with -std=c99 or -std=gnu99 clashes with Bionic headers -HAVE_CAN_STDG99=0 -HAVE_CAN_STDC99=0 -export HAVE_CAN_STDG99 HAVE_CAN_STDC99 +TARGET_OS=Android # Android-x86 does not have helper functions for ProPolice SSP # and AOSP adds the flags by itself (same for warning flags) @@ -108,16 +178,19 @@ HAVE_CAN_FSTACKPROTECTORALL=0 HAVE_CAN_WALL=0 export HAVE_CAN_FNOSTRICTALIASING HAVE_CAN_FSTACKPROTECTORALL HAVE_CAN_WALL -# disable the mknod(8) built-in to get rid of needing setmode.c -HAVE_MKNOD=0; export HAVE_MKNOD - # even the idea of persistent history on a phone is funny HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY +# this is a run-time check and dependent on the target CPU +# architecture (at _least_!) and cannot be auto-detected, +# so always include the safety check even if unnecessary +HAVE_SILENT_IDIVWRAPV=0; export HAVE_SILENT_IDIVWRAPV + # ... and run it! export CC CPPFLAGS CFLAGS LDFLAGS LIBS TARGET_OS -sh ../src/Build.sh -M +sh ../src/Build.sh $args rv=$? +test x"$args" = x"-r" && exit $rv test x0 = x"$rv" && mv -f Makefrag.inc ../ cd .. rm -rf tmp diff --git a/mkshrc b/mkshrc index 0da5ea6..2951595 100644 --- a/mkshrc +++ b/mkshrc @@ -1,11 +1,13 @@ -# Copyright (c) 2010 -# Thorsten Glaser +# Copyright (c) 2010, 2012, 2013 +# Thorsten Glaser # This file is provided under the same terms as mksh. #- # Minimal /system/etc/mkshrc for Android +# +# Support: https://launchpad.net/mksh -: ${TERM:=vt100} ${HOME:=/data} ${MKSH:=/system/bin/sh} ${HOSTNAME:=android} -: ${SHELL:=$MKSH} ${USER:=$(typeset x=$(id); x=${x#*\(}; print -r -- ${x%%\)*})} +: ${TERM:=vt100} ${HOME:=/data} ${MKSH:=/system/bin/sh} ${HOSTNAME:=$(getprop ro.product.device)} +: ${SHELL:=$MKSH} ${USER:=$(typeset x=$(id); x=${x#*\(}; print -r -- ${x%%\)*})} ${HOSTNAME:=android} if (( USER_ID )); then PS1='$'; else PS1='#'; fi function precmd { typeset e=$? @@ -13,12 +15,37 @@ function precmd { (( e )) && print -n "$e|" } PS1='$(precmd)$USER@$HOSTNAME:${PWD:-?} '"$PS1 " -export HOME HOSTNAME MKSH PS1 SHELL TERM USER +export HOME HOSTNAME MKSH SHELL TERM USER alias l='ls' alias la='l -a' alias ll='l -l' alias lo='l -a -l' +function hd { + cat "$@" | command hd /proc/self/fd/0 +} + +function more { + 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 +} + +function setenv { + eval export "\"$1\""'="$2"' +} + for p in ~/.bin; do [[ -d $p/. ]] || continue [[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH diff --git a/src/00-NOTE.txt b/src/00-NOTE.txt deleted file mode 100644 index b51d541..0000000 --- a/src/00-NOTE.txt +++ /dev/null @@ -1,22 +0,0 @@ -This is mksh from AnonCVS on 2010-08-24 with the -following files removed: -• Makefile - (not part of regular mksh releases anyway) -• dot.mkshrc - (not needed, we use our own for Android) -• mksh.1 - (manpage; also available from the web) -• setmode.c - (not needed, we don’t use the mknod builtin) -• strlcpy.c - (not needed, bionic provides this) - -The manual page can be downloaded as PDF (ISO A4 paper) from -https://www.mirbsd.org/MirOS/dist/mir/mksh/mksh.pdf or read -online at https://www.mirbsd.org/man1/mksh (HTML). - -The following changes are done to code in this subdirectory -at the moment: -• check.t main.sh: remove the 'stop' alias to 'kill -STOP' - since Android has a built in stop command that the alias - overrides, causing problems with testing tools \ No newline at end of file diff --git a/src/Build.sh b/src/Build.sh index 6561cf6..31c559d 100644 --- a/src/Build.sh +++ b/src/Build.sh @@ -1,7 +1,8 @@ #!/bin/sh -srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $' +srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.622 2013/02/19 18:45:15 tg Exp $' #- -# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013 # Thorsten Glaser # # Provided that these terms and disclaimer and all copyright notices @@ -22,20 +23,26 @@ srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $' # People analysing the output must whitelist conftest.c for any kind # of compiler warning checks (mirtoconf is by design not quiet). # -# Environment used: CC CFLAGS CPPFLAGS LDFLAGS LIBS NOWARN NROFF -# TARGET_OS TARGET_OSREV -# Feature selectors: USE_PRINTF_BUILTIN -# CPPFLAGS recognised: MKSH_ASSUME_UTF8 MKSH_BINSHREDUCED MKSH_CLS_STRING -# MKSH_CONSERVATIVE_FDS MKSH_MIDNIGHTBSD01ASH_COMPAT -# 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 +# Used environment documentation is at the end of this file. LC_ALL=C export LC_ALL +case $ZSH_VERSION:$VERSION in +:zsh*) ZSH_VERSION=2 ;; +esac + +if test -n "${ZSH_VERSION+x}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: +fi + +if test -d /usr/xpg4/bin/. >/dev/null 2>&1; then + # Solaris: some of the tools have weird behaviour, use portable ones + PATH=/usr/xpg4/bin:$PATH + export PATH +fi + v() { $e "$*" eval "$@" @@ -55,24 +62,13 @@ vq() { rmf() { for _f in "$@"; do - case ${_f} in - mksh.1) ;; - *) rm -f "${_f}" ;; + case $_f in + Build.sh|check.pl|check.t|dot.mkshrc|*.c|*.h|mksh.1) ;; + *) rm -f "$_f" ;; esac done } -if test -d /usr/xpg4/bin/. >/dev/null 2>&1; then - # Solaris: some of the tools have weird behaviour, use portable ones - PATH=/usr/xpg4/bin:$PATH - export PATH -fi - -if test -n "${ZSH_VERSION+x}" && (emulate sh) >/dev/null 2>&1; then - emulate sh - NULLCMD=: -fi - allu=QWERTYUIOPASDFGHJKLZXCVBNM alll=qwertyuiopasdfghjklzxcvbnm alln=0123456789 @@ -156,14 +152,14 @@ ac_testinit() { } # pipe .c | ac_test[n] [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput -ac_testn() { +ac_testnnd() { if test x"$1" = x"!"; then fr=1 shift else fr=0 fi - ac_testinit "$@" || return + ac_testinit "$@" || return 1 cat >conftest.c vv ']' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN conftest.c $LIBS $ccpr" test $tcfn = no && test -f a.out && tcfn=a.out @@ -182,6 +178,10 @@ ac_testn() { test $ct = sunpro && vscan='-e ignored -e turned.off' fi test -n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr + return 0 +} +ac_testn() { + ac_testnnd "$@" || return rmf conftest.c conftest.o ${tcfn}* vv.out ac_testdone } @@ -267,9 +267,24 @@ ac_header() { fi hf=$1; shift hv=`echo "$hf" | tr -d '\012\015' | tr -c $alll$allu$alln $alls` + echo "/* NeXTstep bug workaround */" >x for i do - echo "#include <$i>" >>x + case $i in + _time) + echo '#if HAVE_BOTH_TIME_H' >>x + echo '#include ' >>x + echo '#include ' >>x + echo '#elif HAVE_SYS_TIME_H' >>x + echo '#include ' >>x + echo '#elif HAVE_TIME_H' >>x + echo '#include ' >>x + echo '#endif' >>x + ;; + *) + echo "#include <$i>" >>x + ;; + esac done echo "#include <$hf>" >>x echo 'int main(void) { return (0); }' >>x @@ -279,6 +294,12 @@ ac_header() { } addsrcs() { + addsrcs_s=0 + if test x"$1" = x"-s"; then + # optstatic + addsrcs_s=1 + shift + fi if test x"$1" = x"!"; then fr=0 shift @@ -286,6 +307,13 @@ addsrcs() { fr=1 fi eval i=\$$1 + if test $addsrcs_s = 1; then + if test -f "$2" || test -f "$srcdir/$2"; then + # always add $2, since it exists + fr=1 + i=1 + fi + fi test $fr = "$i" && case " $SRCS " in *\ $2\ *) ;; *) SRCS="$SRCS $2" ;; @@ -293,16 +321,10 @@ addsrcs() { } -if test -d mksh || test -d mksh.exe; then - echo "$me: Error: ./mksh is a directory!" >&2 - exit 1 -fi -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 "$srcdir" || srcdir=. -dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h` +curdir=`pwd` srcdir=`dirname "$0" 2>/dev/null` check_categories= +test -n "$srcdir" || srcdir=. # in case dirname does not exist +dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\([^"]*\)".*$/\1/p' $srcdir/sh.h` +add_cppflags -DMKSH_BUILDSH e=echo r=0 @@ -311,6 +333,8 @@ pm=0 cm=normal optflags=-std-compile-opts last= +tfn= +legacy=0 for i do @@ -327,6 +351,10 @@ do optflags=$i last= ;; + t:*) + tfn=$i + last= + ;; :-c) last=c ;; @@ -338,6 +366,12 @@ do :-j) pm=1 ;; + :-L) + legacy=1 + ;; + :+L) + legacy=0 + ;; :-M) cm=makefile ;; @@ -353,6 +387,9 @@ do :-r) r=1 ;; + :-t) + last=t + ;; :-v) echo "Build.sh $srcversion" echo "for mksh $dstversion" @@ -373,17 +410,47 @@ if test -n "$last"; then exit 1 fi -SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c" -SRCS="$SRCS jobs.c lex.c main.c misc.c shf.c syn.c tree.c var.c" +test -z "$tfn" && if test $legacy = 0; then + tfn=mksh +else + tfn=lksh +fi +if test -d $tfn || test -d $tfn.exe; then + echo "$me: Error: ./$tfn is a directory!" >&2 + exit 1 +fi +rmf a.exe* a.out* conftest.c *core core.* lft ${tfn}* no *.bc *.ll *.o \ + Rebuild.sh signames.inc test.sh x vv.out + +SRCS="lalloc.c eval.c exec.c expr.c funcs.c histrap.c jobs.c" +SRCS="$SRCS lex.c main.c misc.c shf.c syn.c tree.c var.c" + +if test $legacy = 0; then + SRCS="$SRCS edit.c" + check_categories="$check_categories shell:legacy-no int:32" +else + check_categories="$check_categories shell:legacy-yes" + add_cppflags -DMKSH_LEGACY_MODE + HAVE_PERSISTENT_HISTORY=0 + HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1 # from sh.h +fi if test x"$srcdir" = x"."; then CPPFLAGS="-I. $CPPFLAGS" else CPPFLAGS="-I. -I'$srcdir' $CPPFLAGS" fi +test -n "$LDSTATIC" && if test -n "$LDFLAGS"; then + LDFLAGS="$LDFLAGS $LDSTATIC" +else + LDFLAGS=$LDSTATIC +fi -test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname` -if test x"$TARGET_OS" = x""; then +if test -z "$TARGET_OS"; then + x=`uname -s 2>/dev/null || uname` + test x"$x" = x"`uname -n 2>/dev/null`" || TARGET_OS=$x +fi +if test -z "$TARGET_OS"; then echo "$me: Set TARGET_OS, your uname is broken!" >&2 exit 1 fi @@ -391,7 +458,7 @@ oswarn= ccpc=-Wc, ccpl=-Wl, tsts= -ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done' +ccpr='|| for _f in ${tcfn}*; do case $_f in Build.sh|check.pl|check.t|dot.mkshrc|*.c|*.h|mksh.1) ;; *) rm -f "$_f" ;; esac; done' # Evil hack if test x"$TARGET_OS" = x"Android"; then @@ -399,29 +466,105 @@ if test x"$TARGET_OS" = x"Android"; then TARGET_OS=Linux fi +# Evil OS +if test x"$TARGET_OS" = x"Minix"; then + echo >&2 " +WARNING: additional checks before running Build.sh required! +You can avoid these by calling Build.sh correctly, see below. +" + cat >conftest.c <<'EOF' +#include +const char * +#ifdef _NETBSD_SOURCE +ct="Ninix3" +#else +ct="Minix3" +#endif +; +EOF + ct=unknown + vv ']' "${CC-cc} -E $CFLAGS $CPPFLAGS $NOWARN conftest.c | grep ct= | tr -d \\\\015 >x" + sed 's/^/[ /' x + eval `cat x` + rmf x vv.out + case $ct in + Minix3|Ninix3) + echo >&2 " +Warning: you set TARGET_OS to $TARGET_OS but that is ambiguous. +Please set it to either Minix3 or Ninix3, whereas the latter is +all versions of Minix with even partial NetBSD(R) userland. The +value determined from your compiler for the current compilation +(which may be wrong) is: $ct +" + TARGET_OS=$ct + ;; + *) + echo >&2 " +Warning: you set TARGET_OS to $TARGET_OS but that is ambiguous. +Please set it to either Minix3 or Ninix3, whereas the latter is +all versions of Minix with even partial NetBSD(R) userland. The +proper value couldn't be determined, continue at your own risk. +" + ;; + esac +fi + # Configuration depending on OS revision, on OSes that need them case $TARGET_OS in -QNX) +NEXTSTEP) + test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`hostinfo 2>&1 | \ + grep 'NeXT Mach [0-9][0-9.]*:' | \ + sed 's/^.*NeXT Mach \([0-9][0-9.]*\):.*$/\1/'` + ;; +QNX|SCO_SV) test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r` ;; esac # Configuration depending on OS name case $TARGET_OS in +386BSD) + : ${HAVE_CAN_OTWO=0} + add_cppflags -DMKSH_NO_SIGSETJMP + add_cppflags -DMKSH_TYPEDEF_SIG_ATOMIC_T=int + add_cppflags -DMKSH_CONSERVATIVE_FDS + ;; AIX) add_cppflags -D_ALL_SOURCE : ${HAVE_SETLOCALE_CTYPE=0} ;; BeOS) - oswarn=' and will currently not work' + case $KSH_VERSION in + *MIRBSD\ KSH*) + oswarn="; it has minor issues" + ;; + *) + oswarn="; you must recompile mksh with" + oswarn="$oswarn${nl}itself in a second stage" + ;; + esac + # BeOS has no real tty either + add_cppflags -DMKSH_UNEMPLOYED + add_cppflags -DMKSH_DISABLE_TTY_WARNING + # BeOS doesn't have different UIDs and GIDs + add_cppflags -DMKSH__NO_SETEUGID ;; BSD/OS) : ${HAVE_SETLOCALE_CTYPE=0} ;; +Coherent) + oswarn="; it has major issues" + add_cppflags -DMKSH__NO_SYMLINK + check_categories="$check_categories nosymlink" + add_cppflags -DMKSH__NO_SETEUGID + add_cppflags -DMKSH_CONSERVATIVE_FDS + add_cppflags -DMKSH_DISABLE_TTY_WARNING + ;; CYGWIN*) : ${HAVE_SETLOCALE_CTYPE=0} ;; Darwin) + add_cppflags -D_DARWIN_C_SOURCE ;; DragonFly) ;; @@ -430,6 +573,7 @@ FreeBSD) FreeMiNT) oswarn="; it has minor issues" add_cppflags -D_GNU_SOURCE + add_cppflags -DMKSH_CONSERVATIVE_FDS : ${HAVE_SETLOCALE_CTYPE=0} ;; GNU) @@ -437,8 +581,8 @@ GNU) *tendracc*) ;; *) add_cppflags -D_GNU_SOURCE ;; esac - # define NO_PATH_MAX to use Hurd-only functions - add_cppflags -DNO_PATH_MAX + # define MKSH__NO_PATH_MAX to use Hurd-only functions + add_cppflags -DMKSH__NO_PATH_MAX ;; GNU/kFreeBSD) case $CC in @@ -447,7 +591,7 @@ GNU/kFreeBSD) esac ;; Haiku) - add_cppflags -DMKSH_ASSUME_UTF8 + add_cppflags -DMKSH_ASSUME_UTF8; HAVE_ISSET_MKSH_ASSUME_UTF8=1 ;; HP-UX) ;; @@ -469,9 +613,20 @@ Linux) add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN : ${HAVE_REVOKE=0} ;; +LynxOS) + oswarn="; it has minor issues" + ;; MidnightBSD) ;; -Minix) +Minix-vmd) + add_cppflags -DMKSH__NO_SETEUGID + add_cppflags -DMKSH_UNEMPLOYED + add_cppflags -DMKSH_CONSERVATIVE_FDS + add_cppflags -D_MINIX_SOURCE + oldish_ed=no-stderr-ed # no /bin/ed, maybe see below + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +Minix3) add_cppflags -DMKSH_UNEMPLOYED add_cppflags -DMKSH_CONSERVATIVE_FDS add_cppflags -DMKSH_NO_LIMITS @@ -482,11 +637,37 @@ Minix) MirBSD) ;; MSYS_*) - # probably same as CYGWIN* – need to test; from RT|Chatzilla - oswarn='but will probably work' + add_cppflags -DMKSH_ASSUME_UTF8=0; HAVE_ISSET_MKSH_ASSUME_UTF8=1 + # almost same as CYGWIN* (from RT|Chatzilla) + : ${HAVE_SETLOCALE_CTYPE=0} + # broken on this OE (from ir0nh34d) + : ${HAVE_STDINT_H=0} ;; NetBSD) ;; +NEXTSTEP) + add_cppflags -D_NEXT_SOURCE + add_cppflags -D_POSIX_SOURCE + : ${AWK=gawk} ${CC=cc -posix} + add_cppflags -DMKSH_NO_SIGSETJMP + # NeXTstep cannot get a controlling tty + add_cppflags -DMKSH_UNEMPLOYED + case $TARGET_OSREV in + 4.2*) + # OpenStep 4.2 is broken by default + oswarn="; it needs libposix.a" + ;; + esac + add_cppflags -DMKSH_CONSERVATIVE_FDS + ;; +Ninix3) + # similar to Minix3 + add_cppflags -DMKSH_UNEMPLOYED + add_cppflags -DMKSH_CONSERVATIVE_FDS + add_cppflags -DMKSH_NO_LIMITS + # but no idea what else could be needed + oswarn="; it has unknown issues" + ;; OpenBSD) : ${HAVE_SETLOCALE_CTYPE=0} ;; @@ -503,7 +684,8 @@ Plan9) add_cppflags -D_LIMITS_EXTENSION add_cppflags -D_BSD_EXTENSION add_cppflags -D_SUSV2_SOURCE - add_cppflags -DMKSH_ASSUME_UTF8 + add_cppflags -DMKSH_ASSUME_UTF8; HAVE_ISSET_MKSH_ASSUME_UTF8=1 + add_cppflags -DMKSH_NO_CMDLINE_EDITING oswarn=' and will currently not work' add_cppflags -DMKSH_UNEMPLOYED ;; @@ -514,6 +696,7 @@ PW32*) ;; QNX) add_cppflags -D__NO_EXT_QNX + add_cppflags -D__EXT_UNIX_MISC case $TARGET_OSREV in [012345].*|6.[0123].*|6.4.[01]) oldish_ed=no-stderr-ed # oldish /bin/ed is broken @@ -521,19 +704,46 @@ QNX) esac : ${HAVE_SETLOCALE_CTYPE=0} ;; +SCO_SV) + case $TARGET_OSREV in + 3.2*) + # SCO OpenServer 5 + add_cppflags -DMKSH_UNEMPLOYED + ;; + 5*) + # SCO OpenServer 6 + ;; + *) + oswarn='; this is an unknown version of' + oswarn="$oswarn$nl$TARGET_OS ${TARGET_OSREV}, please tell me what to do" + ;; + esac + add_cppflags -DMKSH_CONSERVATIVE_FDS + : ${HAVE_SYS_SIGLIST=0} ${HAVE__SYS_SIGLIST=0} + ;; +skyos) + oswarn="; it has minor issues" + ;; SunOS) add_cppflags -D_BSD_SOURCE add_cppflags -D__EXTENSIONS__ ;; syllable) add_cppflags -D_GNU_SOURCE + add_cppflags -DMKSH_NO_SIGSUSPEND oswarn=' and will currently not work' ;; ULTRIX) : ${CC=cc -YPOSIX} - add_cppflags -Dssize_t=int + add_cppflags -DMKSH_TYPEDEF_SSIZE_T=int + add_cppflags -DMKSH_CONSERVATIVE_FDS : ${HAVE_SETLOCALE_CTYPE=0} ;; +UnixWare|UNIX_SV) + # SCO UnixWare + add_cppflags -DMKSH_CONSERVATIVE_FDS + : ${HAVE_SYS_SIGLIST=0} ${HAVE__SYS_SIGLIST=0} + ;; UWIN*) ccpc='-Yc,' ccpl='-Yl,' @@ -542,14 +752,21 @@ UWIN*) oswarn="$oswarn${nl}platform itself is very flakey/unreliable" : ${HAVE_SETLOCALE_CTYPE=0} ;; +_svr4) + # generic target for SVR4 Unix with uname -s = uname -n + # this duplicates the * target below + oswarn='; it may or may not work' + test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r` + ;; *) oswarn='; it may or may not work' + test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r` ;; esac : ${HAVE_MKNOD=0} -: ${CC=cc} ${NROFF=nroff} +: ${AWK=awk} ${CC=cc} ${NROFF=nroff} test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \ NROFF="$NROFF -c" @@ -572,6 +789,10 @@ OSF1) vv '|' "uname -a >&2" vv '|' "/usr/sbin/sizer -v >&2" ;; +SCO_SV|UnixWare|UNIX_SV) + vv '|' "uname -a >&2" + vv '|' "uname -X >&2" + ;; *) vv '|' "uname -a >&2" ;; @@ -582,7 +803,7 @@ operating system '$TARGET_OS'$oswarn. If you can provide a shell account to the developer, this may improve; please drop us a success or failure notice or even send in diffs. " -$e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao" +$e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao on $TARGET_OS ${TARGET_OSREV}..." # # Begin of mirtoconf checks @@ -600,62 +821,77 @@ $e $bi$me: Scanning for functions... please ignore any errors.$ao CPP="$CC -E" $e ... which compiler seems to be used cat >conftest.c <<'EOF' +const char * #if defined(__ICC) || defined(__INTEL_COMPILER) -ct=icc +ct="icc" #elif defined(__xlC__) || defined(__IBMC__) -ct=xlc +ct="xlc" #elif defined(__SUNPRO_C) -ct=sunpro +ct="sunpro" #elif defined(__ACK__) -ct=ack +ct="ack" #elif defined(__BORLANDC__) -ct=bcc +ct="bcc" #elif defined(__WATCOMC__) -ct=watcom +ct="watcom" #elif defined(__MWERKS__) -ct=metrowerks +ct="metrowerks" #elif defined(__HP_cc) -ct=hpcc +ct="hpcc" #elif defined(__DECC) || (defined(__osf__) && !defined(__GNUC__)) -ct=dec +ct="dec" #elif defined(__PGI) -ct=pgi +ct="pgi" #elif defined(__DMC__) -ct=dmc +ct="dmc" #elif defined(_MSC_VER) -ct=msc +ct="msc" #elif defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__) -ct=adsp +ct="adsp" #elif defined(__IAR_SYSTEMS_ICC__) -ct=iar +ct="iar" #elif defined(SDCC) -ct=sdcc +ct="sdcc" #elif defined(__PCC__) -ct=pcc +ct="pcc" #elif defined(__TenDRA__) -ct=tendra +ct="tendra" #elif defined(__TINYC__) -ct=tcc +ct="tcc" #elif defined(__llvm__) && defined(__clang__) -ct=clang +ct="clang" #elif defined(__NWCC__) -ct=nwcc +ct="nwcc" #elif defined(__GNUC__) -ct=gcc +ct="gcc" #elif defined(_COMPILER_VERSION) -ct=mipspro +ct="mipspro" #elif defined(__sgi) -ct=mipspro +ct="mipspro" #elif defined(__hpux) || defined(__hpua) -ct=hpcc +ct="hpcc" #elif defined(__ultrix) -ct=ucode +ct="ucode" +#elif defined(__USLC__) +ct="uslc" +#elif defined(__LCC__) +ct="lcc" #else -ct=unknown +ct="unknown" #endif +; +const char * +#if defined(__KLIBC__) +et="klibc" +#else +et="unknown" +#endif +; EOF -ct=unknown -vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c | grep ct= | tr -d \\\\015 >x" +ct=untested +et=untested +vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c | \ + sed -n '/^ *[ce]t *= */s/^ *\([ce]t\) *= */\1=/p' | tr -d \\\\015 >x" sed 's/^/[ /' x eval `cat x` rmf x vv.out @@ -679,8 +915,9 @@ bcc) clang) # does not work with current "ccc" compiler driver vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version" - # this works, for now + # one of these two works, for now vv '|' "${CLANG-clang} -version" + vv '|' "${CLANG-clang} --version" # ensure compiler and linker are in sync unless overridden case $CCC_CC:$CCC_LD in :*) ;; @@ -715,6 +952,10 @@ iar) icc) vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V" ;; +lcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS" + add_cppflags -D__inline__=__inline + ;; metrowerks) echo >&2 'Warning: Metrowerks C compiler detected. This has not yet been tested for compatibility with mksh. Continue at your @@ -773,10 +1014,18 @@ ucode) vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V" vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS" ;; +uslc) + case $TARGET_OS:$TARGET_OSREV in + SCO_SV:3.2*) + # SCO OpenServer 5 + CFLAGS="$CFLAGS -g" + : ${HAVE_CAN_OTWO=0} ${HAVE_CAN_OPTIMISE=0} + ;; + esac + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS" + ;; watcom) - echo >&2 'Warning: Watcom C Compiler detected. This compiler has not yet - been tested for compatibility with mksh. Continue at your - own risk, please report success/failure to the developers.' + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS" ;; xlc) vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion" @@ -784,7 +1033,11 @@ xlc) vv '|' "ld -V" ;; *) + test x"$ct" = x"untested" && $e "!!! detecting preprocessor failed" ct=unknown + vv "$CC --version" + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS" + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS" ;; esac case $cm in @@ -792,7 +1045,19 @@ dragonegg|llvm) vv '|' "llc -version" ;; esac -$e "$bi==> which compiler seems to be used...$ao $ui$ct$ao" +case $et in +klibc) + add_cppflags -DMKSH_NO_LIMITS + ;; +unknown) + # nothing special detected, don’t worry + unset et + ;; +*) + # huh? + ;; +esac +$e "$bi==> which compiler seems to be used...$ao $ui$ct${et+ on $et}$ao" rmf conftest.c conftest.o conftest a.out* a.exe* vv.out # @@ -831,6 +1096,7 @@ if ac_ifcpp 'ifdef __TINYC__' couldbe_tcc '!' compiler_known 0 \ 'if this could be tcc'; then ct=tcc CPP='cpp -D__TINYC__' + HAVE_COMPILER_KNOWN=1 fi if test $ct = sunpro; then @@ -864,6 +1130,9 @@ elif test $ct = tendra; then elif test $ct = ucode; then save_NOWARN= DOWARN=-w2 +elif test $ct = watcom; then + save_NOWARN= + DOWARN=-Wc,-we else test x"$save_NOWARN" = x"" && save_NOWARN=-Wno-error ac_flags 0 wnoerror "$save_NOWARN" @@ -908,9 +1177,11 @@ i=0 if test $ct = gcc; then # The following tests run with -Werror (gcc only) if possible NOWARN=$DOWARN; phase=u + ac_flags 0 wnooverflow -Wno-overflow + # mksh is not written in CFrustFrust! + ac_flags 1 no_eh_frame -fno-asynchronous-unwind-tables ac_flags 1 fnostrictaliasing -fno-strict-aliasing ac_flags 1 fstackprotectorall -fstack-protector-all - ac_flags 1 fwrapv -fwrapv test $cm = dragonegg && case " $CC $CFLAGS $LDFLAGS " in *\ -fplugin=*dragonegg*) ;; *) ac_flags 1 fplugin_dragonegg -fplugin=dragonegg ;; @@ -960,13 +1231,12 @@ elif test $ct = icc; then elif test $ct = sunpro; then phase=u ac_flags 1 v -v - ac_flags 1 xc99 -xc99 'for support of ISO C99' ac_flags 1 ipo -xipo 'for cross-module optimisation' phase=x elif test $ct = hpcc; then phase=u - ac_flags 1 agcc -Agcc 'for support of GCC extensions' - ac_flags 1 ac99 -AC99 'for support of ISO C99' + # probably not needed + #ac_flags 1 agcc -Agcc 'for support of GCC extensions' phase=x elif test $ct = dec; then ac_flags 0 verb -verbose @@ -977,7 +1247,6 @@ elif test $ct = dmc; then elif test $ct = bcc; then ac_flags 1 strpool "${ccpc}-d" 'if string pooling can be enabled' elif test $ct = mipspro; then - ac_flags 1 xc99 -c99 'for support of ISO C99' ac_flags 1 fullwarn -fullwarn 'for remark output support' elif test $ct = msc; then ac_flags 1 strpool "${ccpc}/GF" 'if string pooling can be enabled' @@ -989,11 +1258,9 @@ elif test $ct = msc; then ac_flags 1 wall "${ccpc}/Wall" 'to enable all warnings' ac_flags 1 wp64 "${ccpc}/Wp64" 'to enable 64-bit warnings' elif test $ct = xlc; then - ac_flags 1 x99 -qlanglvl=extc99 - test 1 = $HAVE_CAN_X99 || ac_flags 1 c99 -qlanglvl=stdc99 ac_flags 1 rodata "-qro -qroconst -qroptr" ac_flags 1 rtcheck -qcheck=all - ac_flags 1 rtchkc -qextchk + #ac_flags 1 rtchkc -qextchk # reported broken ac_flags 1 wformat "-qformat=all -qformat=nozln" #ac_flags 1 wp64 -qwarn64 # too verbose for now elif test $ct = tendra; then @@ -1001,19 +1268,17 @@ elif test $ct = tendra; then test 1 = $HAVE_CAN_YSYSTEM && CPPFLAGS="-Ysystem $CPPFLAGS" ac_flags 1 extansi -Xa elif test $ct = tcc; then - ac_flags 1 boundschk -b + : #broken# ac_flags 1 boundschk -b elif test $ct = clang; then i=1 elif test $ct = nwcc; then i=1 - #broken# ac_flags 1 ssp -stackprotect + : #broken# ac_flags 1 ssp -stackprotect fi # flags common to a subset of compilers (run with -Werror on gcc) if test 1 = $i; then - ac_flags 1 stdg99 -std=gnu99 'for support of ISO C99 + GCC extensions' - test 1 = $HAVE_CAN_STDG99 || \ - ac_flags 1 stdc99 -std=c99 'for support of ISO C99' ac_flags 1 wall -Wall + ac_flags 1 fwrapv -fwrapv fi phase=x @@ -1036,6 +1301,10 @@ ac_test attribute_bounded '' 'for __attribute__((__bounded__))' <<-'EOF' __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) { + /* + * if memmove does not exist, we are not on a system + * with GCC with __bounded__ attribute either so poo + */ memmove(d, s, n); return ((int)n); } #endif @@ -1054,20 +1323,6 @@ ac_test attribute_format '' 'for __attribute__((__format__))' <<-'EOF' 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(__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) { 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(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2)) /* force a failure: TenDRA and gcc 1.42 have false positive here */ @@ -1108,24 +1363,6 @@ phase=x # if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \ "if a reduced-feature mksh is requested"; then - #XXX this sucks; fix it for *all* compilers - case $ct in - clang|icc|nwcc) - ac_flags 1 fnoinline -fno-inline - ;; - gcc) - NOWARN=$DOWARN; phase=u - ac_flags 1 fnoinline -fno-inline - NOWARN=$save_NOWARN; phase=x - ;; - sunpro) - ac_flags 1 fnoinline -xinline= - ;; - xlc) - ac_flags 1 fnoinline -qnoinline - ;; - esac - : ${HAVE_NICE=0} : ${HAVE_PERSISTENT_HISTORY=0} check_categories="$check_categories smksh" @@ -1145,15 +1382,34 @@ ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \ ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \ 'if traditional/conservative fd use is requested' && \ check_categories="$check_categories convfds" +#ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \ +# "if deprecated features are to be omitted" && \ +# check_categories="$check_categories nodeprecated" +#ac_ifcpp 'ifdef MKSH_DISABLE_EXPERIMENTAL' isset_MKSH_DISABLE_EXPERIMENTAL '' \ +# "if experimental features are to be omitted" && \ +# check_categories="$check_categories noexperimental" +ac_ifcpp 'ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT' isset_MKSH_MIDNIGHTBSD01ASH_COMPAT '' \ + 'if the MidnightBSD 0.1 ash compatibility mode is requested' && \ + check_categories="$check_categories mnbsdash" # # Environment: headers # +ac_header sys/time.h sys/types.h +ac_header time.h sys/types.h +test "11" = "$HAVE_SYS_TIME_H$HAVE_TIME_H" || HAVE_BOTH_TIME_H=0 +ac_test both_time_h '' 'whether and can both be included' <<-'EOF' + #include + #include + #include + int main(void) { struct tm tm; return ((int)sizeof(tm)); } +EOF 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/resource.h sys/types.h _time ac_header sys/select.h sys/types.h ac_header sys/sysmacros.h ac_header bstring.h @@ -1164,54 +1420,10 @@ ac_header paths.h ac_header stdint.h stdarg.h # include strings.h only if compatible with string.h ac_header strings.h sys/types.h string.h +ac_header termios.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 # @@ -1259,8 +1471,17 @@ EOF ac_test rlim_t <<-'EOF' #include + #if HAVE_BOTH_TIME_H #include + #include + #elif HAVE_SYS_TIME_H + #include + #elif HAVE_TIME_H + #include + #endif + #if HAVE_SYS_RESOURCE_H #include + #endif #include int main(void) { return ((int)(rlim_t)0); } EOF @@ -1299,18 +1520,80 @@ test 1 = $HAVE_SIG_T || add_cppflags -Dsig_t=nosig_t ac_cppflags SIG_T # -# Environment: signals +# 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.622 2013/02/19 18:45:15 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 $tfn.s + test $fv = 0 || v "llvm-link -o - conftest.o | opt $optflags | llc -o $tfn.s" || fv=0 + test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn $tfn.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 $tfn.s + test $fv = 0 || v "llvm-link -o - conftest.bc | opt $optflags | llc -o $tfn.s" || fv=0 + test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn $tfn.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: errors and signals # test x"NetBSD" = x"$TARGET_OS" && $e Ignore the compatibility warning. +ac_testn sys_errlist '' "the sys_errlist[] array and sys_nerr" <<-'EOF' + extern const int sys_nerr; + extern const char * const sys_errlist[]; + int main(void) { return (*sys_errlist[sys_nerr - 1]); } +EOF +ac_testn _sys_errlist '!' sys_errlist 0 "the _sys_errlist[] array and _sys_nerr" <<-'EOF' + extern const int _sys_nerr; + extern const char * const _sys_errlist[]; + int main(void) { return (*_sys_errlist[_sys_nerr - 1]); } +EOF +if test 1 = "$HAVE__SYS_ERRLIST"; then + add_cppflags -Dsys_nerr=_sys_nerr + add_cppflags -Dsys_errlist=_sys_errlist + HAVE_SYS_ERRLIST=1 +fi +ac_cppflags SYS_ERRLIST + for what in name list; do uwhat=`upper $what` ac_testn sys_sig$what '' "the sys_sig${what}[] array" <<-EOF - extern const char *const sys_sig${what}[]; + extern const char * const sys_sig${what}[]; int main(void) { return (sys_sig${what}[0][0]); } EOF ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig${what}[] array" <<-EOF - extern const char *const _sys_sig${what}[]; + extern const char * const _sys_sig${what}[]; int main(void) { return (_sys_sig${what}[0][0]); } EOF eval uwhat_v=\$HAVE__SYS_SIG$uwhat @@ -1321,28 +1604,27 @@ for what in name list; do ac_cppflags SYS_SIG$uwhat done -ac_test strsignal '!' sys_siglist 0 <<-'EOF' - #include - #include - int main(void) { return (strsignal(1)[0]); } -EOF - # # Environment: library functions # -ac_testn flock_ex '' 'flock and mmap' <<-'EOF' +ac_test flock <<-'EOF' #include + #include + #undef flock #if HAVE_SYS_FILE_H #include #endif - #if HAVE_SYS_MMAN_H - #include - #endif + int main(void) { return (flock(0, LOCK_EX | LOCK_UN)); } +EOF + +ac_test lock_fcntl '!' flock 1 'whether we can lock files with fcntl' <<-'EOF' #include - #include - int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX), - PROT_READ, MAP_PRIVATE, 0, (off_t)0) == (void *)NULL ? 1 : - munmap(NULL, 0)); } + #undef flock + int main(void) { + struct flock lks; + lks.l_type = F_WRLCK | F_UNLCK; + return (fcntl(0, F_SETLKW, &lks)); + } EOF ac_test getrusage <<-'EOF' @@ -1355,11 +1637,29 @@ ac_test getrusage <<-'EOF' } EOF +ac_test gettimeofday <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + int main(void) { struct timeval tv; return (gettimeofday(&tv, NULL)); } +EOF + ac_test killpg <<-'EOF' #include int main(int ac, char *av[]) { return (av[0][killpg(123, ac)]); } EOF +ac_test memmove <<-'EOF' + #include + #include + #include + #if HAVE_STRINGS_H + #include + #endif + int main(int ac, char *av[]) { + return (*(int *)(void *)memmove(av[0], av[1], ac)); + } +EOF + ac_test mknod '' 'if to use mknod(), makedev() and friends' <<-'EOF' #define MKSH_INCLUDES_ONLY #include "sh.h" @@ -1371,10 +1671,19 @@ ac_test mknod '' 'if to use mknod(), makedev() and friends' <<-'EOF' } EOF -ac_test mkstemp <<-'EOF' +ac_test mmap lock_fcntl 0 'for mmap and munmap' <<-'EOF' + #include + #if HAVE_SYS_FILE_H + #include + #endif + #if HAVE_SYS_MMAN_H + #include + #endif + #include #include - #include - int main(void) { char tmpl[] = "X"; return (mkstemp(tmpl)); } + int main(void) { return ((void *)mmap(NULL, (size_t)0, + PROT_READ, MAP_PRIVATE, 0, (off_t)0) == (void *)NULL ? 1 : + munmap(NULL, 0)); } EOF ac_test nice <<-'EOF' @@ -1405,7 +1714,14 @@ EOF ac_test select <<-'EOF' #include + #if HAVE_BOTH_TIME_H #include + #include + #elif HAVE_SYS_TIME_H + #include + #elif HAVE_TIME_H + #include + #endif #if HAVE_SYS_BSDTYPES_H #include #endif @@ -1444,16 +1760,47 @@ ac_test setgroups setresugid 0 <<-'EOF' int main(void) { gid_t gid = 0; return (setgroups(0, &gid)); } EOF -ac_test strcasestr <<-'EOF' - #include - #include +if test x"$et" = x"klibc"; then + + ac_testn __rt_sigsuspend '' 'whether klibc uses RT signals' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + extern int __rt_sigsuspend(const sigset_t *, size_t); + int main(void) { return (__rt_sigsuspend(NULL, 0)); } +EOF + + # no? damn! legacy crap ahead! + + ac_testn __sigsuspend_s '!' __rt_sigsuspend 1 \ + 'whether sigsuspend is usable (1/2)' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + extern int __sigsuspend_s(sigset_t); + int main(void) { return (__sigsuspend_s(0)); } +EOF + ac_testn __sigsuspend_xxs '!' __sigsuspend_s 1 \ + 'whether sigsuspend is usable (2/2)' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + extern int __sigsuspend_xxs(int, int, sigset_t); + int main(void) { return (__sigsuspend_xxs(0, 0, 0)); } +EOF + + if test "000" = "$HAVE___RT_SIGSUSPEND$HAVE___SIGSUSPEND_S$HAVE___SIGSUSPEND_XXS"; then + # no usable sigsuspend(), use pause() *ugh* + add_cppflags -DMKSH_NO_SIGSUSPEND + fi +fi + +ac_test strerror '!' sys_errlist 0 <<-'EOF' + extern char *strerror(int); + int main(int ac, char *av[]) { return (*strerror(*av[ac])); } +EOF + +ac_test strsignal '!' sys_siglist 0 <<-'EOF' #include - #if HAVE_STRINGS_H - #include - #endif - int main(int ac, char *av[]) { - return ((int)(ptrdiff_t)(void *)strcasestr(*av, av[ac])); - } + #include + int main(void) { return (strsignal(1)[0]); } EOF ac_test strlcpy <<-'EOF' @@ -1465,36 +1812,215 @@ EOF # # check headers for declarations # -save_CC=$CC; save_LDFLAGS=$LDFLAGS; save_LIBS=$LIBS -CC="$CC -c -o $tcfn"; LDFLAGS=; LIBS= -ac_test '!' flock_decl flock_ex 1 'if flock() does not need to be declared' <<-'EOF' +ac_test flock_decl flock 1 'for declaration of flock()' <<-'EOF' #define MKSH_INCLUDES_ONLY #include "sh.h" - long flock(void); /* this clashes if defined before */ - int main(void) { return ((int)flock()); } + #if HAVE_SYS_FILE_H + #include + #endif + int main(void) { return ((flock)(0, 0)); } EOF -ac_test '!' revoke_decl revoke 1 'if revoke() does not need to be declared' <<-'EOF' +ac_test revoke_decl revoke 1 'for declaration of revoke()' <<-'EOF' #define MKSH_INCLUDES_ONLY #include "sh.h" - long revoke(void); /* this clashes if defined before */ - int main(void) { return ((int)revoke()); } + int main(void) { return ((revoke)("")); } EOF -ac_test sys_siglist_decl sys_siglist 1 'if sys_siglist[] does not need to be declared' <<-'EOF' +ac_test sys_errlist_decl sys_errlist 0 "for declaration of sys_errlist[] and sys_nerr" <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + int main(void) { return (*sys_errlist[sys_nerr - 1]); } +EOF +ac_test sys_siglist_decl sys_siglist 0 'for declaration of sys_siglist[]' <<-'EOF' #define MKSH_INCLUDES_ONLY #include "sh.h" int main(void) { return (sys_siglist[0][0]); } EOF -CC=$save_CC; LDFLAGS=$save_LDFLAGS; LIBS=$save_LIBS # # other checks # fd='if to use persistent history' -ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1 +ac_cache PERSISTENT_HISTORY || case $HAVE_MMAP$HAVE_FLOCK$HAVE_LOCK_FCNTL in +11*|101) fv=1 ;; +esac test 1 = $fv || check_categories="$check_categories no-histfile" ac_testdone ac_cppflags +save_CFLAGS=$CFLAGS +test x1 = x$HAVE_CAN_WNOOVERFLOW && CFLAGS="$CFLAGS -Wno-overflow" +ac_testn compile_time_asserts_$$ '' 'whether compile-time assertions pass' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + #ifndef CHAR_BIT + #define CHAR_BIT 8 /* defuse this test on really legacy systems */ + #endif + struct ctasserts { + #define cta(name, assertion) char name[(assertion) ? 1 : -1] +/* this one should be defined by the standard */ +cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) && + (sizeof(unsigned char) == 1)); +cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) && + ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF)); +/* the next assertion is probably not really needed */ +cta(short_is_2_char, sizeof(short) == 2); +cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short)); +/* the next assertion is probably not really needed */ +cta(int_is_4_char, sizeof(int) == 4); +cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int)); + +cta(long_ge_int, sizeof(long) >= sizeof(int)); +cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long)); + +#ifndef MKSH_LEGACY_MODE +/* the next assertion is probably not really needed */ +cta(ari_is_4_char, sizeof(mksh_ari_t) == 4); +/* but the next two are; we REQUIRE signed integer wraparound */ +cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1)); +#ifndef MKSH_GCC55009 +cta(ari_sign_32_bit_and_wrap, + (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1) > + (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 2)); +#endif +/* the next assertion is probably not really needed */ +cta(uari_is_4_char, sizeof(mksh_uari_t) == 4); +/* but the next three are; we REQUIRE unsigned integer wraparound */ +cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1)); +cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3)); +cta(uari_wrap_32_bit, + (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) > + (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4)); +#endif +/* these are always required */ +cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0); +cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0); + +cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t)); +cta(ptrdifft_sizet_same_size, sizeof(ptrdiff_t) == sizeof(size_t)); +cta(ptrdifft_voidptr_same_size, sizeof(ptrdiff_t) == sizeof(void *)); +cta(ptrdifft_funcptr_same_size, sizeof(ptrdiff_t) == sizeof(void (*)(void))); +/* our formatting routines assume this */ +cta(ptr_fits_in_long, sizeof(ptrdiff_t) <= sizeof(long)); + }; +#ifndef MKSH_LEGACY_MODE +#ifndef MKSH_GCC55009 +#define NUM 22 +#else +#define NUM 21 +#endif +#else +#define NUM 15 +#endif +char ctasserts_dblcheck[sizeof(struct ctasserts) == NUM ? 1 : -1]; + int main(void) { return (sizeof(ctasserts_dblcheck)); } +EOF +CFLAGS=$save_CFLAGS +eval test 1 = \$HAVE_COMPILE_TIME_ASSERTS_$$ || exit 1 + +# +# extra checks for legacy mksh +# +if test $legacy = 1; then + ac_test long_32bit '' 'whether long is 32 bit wide' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + #ifndef CHAR_BIT + #define CHAR_BIT 0 + #endif + struct ctasserts { + #define cta(name, assertion) char name[(assertion) ? 1 : -1] + cta(char_is_8_bits, (CHAR_BIT) == 8); + cta(long_is_32_bits, sizeof(long) == 4); + }; + int main(void) { return (sizeof(struct ctasserts)); } +EOF + + ac_test long_64bit '!' long_32bit 0 'whether long is 64 bit wide' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + #ifndef CHAR_BIT + #define CHAR_BIT 0 + #endif + struct ctasserts { + #define cta(name, assertion) char name[(assertion) ? 1 : -1] + cta(char_is_8_bits, (CHAR_BIT) == 8); + cta(long_is_64_bits, sizeof(long) == 8); + }; + int main(void) { return (sizeof(struct ctasserts)); } +EOF + + case $HAVE_LONG_32BIT$HAVE_LONG_64BIT in + 10) check_categories="$check_categories int:32" ;; + 01) check_categories="$check_categories int:64" ;; + *) check_categories="$check_categories int:u" ;; + esac +fi + +# +# runtime checks +# once this is more than one, check if we can do runtime +# checks (not cross-compiling) first to save on warnings +# +$e "${bi}run-time checks follow$ao, please ignore any weird errors" + +if ac_testnnd silent_idivwrapv '' '(run-time) whether signed integer division overflows wrap silently' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + #if !defined(MKSH_LEGACY_MODE) || HAVE_LONG_32BIT + #define IDIVWRAPV_VL (mksh_uari_t)0x80000000UL + #elif HAVE_LONG_64BIT + #define IDIVWRAPV_VL (mksh_uari_t)0x8000000000000000UL + #else + # error "cannot check this" + #endif + #ifdef SIGFPE + static void fpe_catcher(int) MKSH_A_NORETURN; + #endif + int main(int ac, char **av) { + mksh_ari_t o1, o2, r1, r2; + + #ifdef SIGFPE + signal(SIGFPE, fpe_catcher); + #endif + o1 = (mksh_ari_t)IDIVWRAPV_VL; + o2 = -ac; + r1 = o1 / o2; + r2 = o1 % o2; + if (r1 == o1 && r2 == 0) { + printf("si"); + return (0); + } + printf("no %d %d %d %d %s", (int)o1, (int)o2, (int)r1, + (int)r2, av[0]); + return (1); + } + #ifdef SIGFPE + static const char fpe_msg[] = "no, got SIGFPE, what were they smoking?"; + #define fpe_msglen (sizeof(fpe_msg) - 1) + static void fpe_catcher(int sig MKSH_A_UNUSED) { + _exit(write(1, fpe_msg, fpe_msglen) == fpe_msglen ? 2 : 3); + } + #endif +EOF +then + if test $fv = 0; then + echo "| hrm, compiling this failed, but we will just failback" + else + echo "| running test programme; this will fail if cross-compiling" + echo "| in which case we will gracefully degrade to the default" + ./$tcfn >vv.out 2>&1 + rv=$? + echo "| result: `cat vv.out`" + fv=0 + test $rv = 0 && test x"`cat vv.out`" = x"si" && fv=1 + fi + rmf conftest.c conftest.o ${tcfn}* vv.out + ac_testdone +fi +ac_cppflags + +$e "${bi}end of run-time checks$ao" + # # Compiler: Praeprocessor (only if needed) # @@ -1525,46 +2051,62 @@ if test 0 = $HAVE_SYS_SIGNAME; then else $e No list of signal names available via cpp. Falling back... fi - sigseen=: + sigseenone=: + sigseentwo=: echo '#include #ifndef NSIG #if defined(_NSIG) #define NSIG _NSIG #elif defined(SIGMAX) #define NSIG (SIGMAX+1) +#elif defined(_SIGMAX) +#define NSIG (_SIGMAX+1) #endif #endif -mksh_cfg: NSIG' >conftest.c +int +mksh_cfg= NSIG +;' >conftest.c + # GNU sed 2.03 segfaults when optimising this to sed -n NSIG=`vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \ - grep mksh_cfg: | sed 's/^mksh_cfg:[ ]*\([0-9x ()+-]*\).*$/\1/'` + grep '^ *mksh_cfg *=' | \ + sed 's/^ *mksh_cfg *=[ ]*\([()0-9x+-][()0-9x+ -]*\).*$/\1/'` case $NSIG in - *[\ \(\)+-]*) NSIG=`awk "BEGIN { print $NSIG }"` ;; + *mksh_cfg*) $e "Error: NSIG='$NSIG'"; NSIG=0 ;; + *[\ \(\)+-]*) NSIG=`"$AWK" "BEGIN { print $NSIG }" /dev/null 2>&1 || printf=echo + test $printf = echo || test "`printf %d 42`" = 42 || printf=echo test $printf = echo || NSIG=`printf %d "$NSIG" 2>/dev/null` $printf "NSIG=$NSIG ... " - sigs="ABRT ALRM BUS CHLD CLD CONT DIL EMT FPE HUP ILL INFO INT IO IOT" - sigs="$sigs KILL LOST PIPE PROF PWR QUIT RESV SAK SEGV STOP SYS TERM" + sigs="INT SEGV ABRT KILL ALRM BUS CHLD CLD CONT DIL EMT FPE HUP ILL" + sigs="$sigs INFO IO IOT LOST PIPE PROF PWR QUIT RESV SAK STOP SYS TERM" sigs="$sigs TRAP TSTP TTIN TTOU URG USR1 USR2 VTALRM WINCH XCPU XFSZ" test 1 = $HAVE_CPP_DD && test $NSIG -gt 1 && sigs="$sigs "`vq \ "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c" | \ - grep '[ ]SIG[A-Z0-9]*[ ]' | \ - sed 's/^\(.*[ ]SIG\)\([A-Z0-9]*\)\([ ].*\)$/\2/' | sort` + grep '[ ]SIG[A-Z0-9][A-Z0-9]*[ ]' | \ + sed 's/^.*[ ]SIG\([A-Z0-9][A-Z0-9]*\)[ ].*$/\1/' | sort` test $NSIG -gt 1 || sigs= for name in $sigs; do + case $sigseenone in + *:$name:*) continue ;; + esac + sigseenone=$sigseenone$name: echo '#include ' >conftest.c - echo mksh_cfg: SIG$name >>conftest.c + echo int >>conftest.c + echo mksh_cfg= SIG$name >>conftest.c + echo ';' >>conftest.c + # GNU sed 2.03 croaks on optimising this, too vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \ - grep mksh_cfg: | \ - sed 's/^mksh_cfg:[ ]*\([0-9x]*\).*$/\1:'$name/ - done | grep -v '^:' | sed 's/:/ /g' | while read nr name; do + grep '^ *mksh_cfg *=' | \ + sed 's/^ *mksh_cfg *=[ ]*\([0-9][0-9x]*\).*$/:\1 '$name/ + done | sed -n '/^:[^ ]/s/^://p' | 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 + case $sigseentwo in *:$nr:*) ;; *) echo " { \"$name\", $nr }," - sigseen=$sigseen$nr: + sigseentwo=$sigseentwo$nr: $printf "$name=$nr " >&2 ;; esac @@ -1573,40 +2115,54 @@ mksh_cfg: NSIG' >conftest.c $e done. fi -addsrcs '!' HAVE_STRLCPY strlcpy.c +addsrcs -s '!' HAVE_STRLCPY strlcpy.c addsrcs USE_PRINTF_BUILTIN printf.c test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose" +test -n "$LDSTATIC" && add_cppflags -DMKSH_OPTSTATIC +add_cppflags -DMKSH_BUILD_R=431 $e $bi$me: Finished configuration testing, now producing output.$ao files= objs= sp= -case $curdir in -*\ *) echo "#!./mksh" >test.sh ;; -*) echo "#!$curdir/mksh" >test.sh ;; +case $tcfn in +a.exe) mkshexe=$tfn.exe ;; +*) mkshexe=$tfn ;; esac -cat >>test.sh <<-EOF +case $curdir in +*\ *) mkshshebang="#!./$mkshexe" ;; +*) mkshshebang="#!$curdir/$mkshexe" ;; +esac +cat >test.sh <<-EOF + $mkshshebang LC_ALL=C PATH='$PATH'; export LC_ALL PATH test -n "\$KSH_VERSION" || exit 1 set -A check_categories -- $check_categories - pflag='$curdir/mksh' + pflag='$curdir/$mkshexe' 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 { + usee=0 Pflag=0 Sflag=0 uset=0 vflag=1 xflag=0 + while getopts "C:e:fPp:QSs:t:v" ch; do case \$ch { (C) check_categories[\${#check_categories[*]}]=\$OPTARG ;; (e) usee=1; eflag=\$OPTARG ;; + (f) check_categories[\${#check_categories[*]}]=fastbox ;; (P) Pflag=1 ;; + (+P) Pflag=0 ;; (p) pflag=\$OPTARG ;; + (Q) vflag=0 ;; + (+Q) vflag=1 ;; + (S) Sflag=1 ;; + (+S) Sflag=0 ;; (s) sflag=\$OPTARG ;; (t) uset=1; tflag=\$OPTARG ;; (v) vflag=1 ;; + (+v) vflag=0 ;; (*) xflag=1 ;; } done shift \$((OPTIND - 1)) - set -A args -- '$srcdir/check.pl' -p "\$pflag" -s "\$sflag" + set -A args -- '$srcdir/check.pl' -p "\$pflag" x= for y in "\${check_categories[@]}"; do x=\$x,\$y @@ -1627,19 +2183,40 @@ cat >>test.sh <<-EOF (( 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' + fgrep -e MirOS: -e MIRBSD "\$sflag" print "This shell is actually:\\n\\t\$KSH_VERSION" print 'test.sh built for mksh $dstversion' cstr='\$os = defined \$^O ? \$^O : "unknown";' cstr="\$cstr"'print \$os . ", Perl version " . \$];' for perli in \$PERL perl5 perl no; do - [[ \$perli = no ]] && exit 1 - perlos=\$(\$perli -e "\$cstr") 2>/dev/null || continue - print "Perl interpreter '\$perli' running on '\$perlos'" - [[ -n \$perlos ]] && break + if [[ \$perli = no ]]; then + print Cannot find a working Perl interpreter, aborting. + exit 1 + fi + print "Trying Perl interpreter '\$perli'..." + perlos=\$(\$perli -e "\$cstr") + rv=\$? + print "Errorlevel \$rv, running on '\$perlos'" + if (( rv )); then + print "=> not using" + continue + fi + if [[ -n \$perlos ]]; then + print "=> using it" + break + fi done - exec \$perli "\${args[@]}" "\$@"$tsts + (( Sflag )) || echo + \$perli "\${args[@]}" -s "\$sflag" "\$@" + (( Sflag )) || exec \$perli "\${args[@]}" -s "\$sflag" "\$@"$tsts + # use of the -S option for check.t split into multiple chunks + rv=0 + for s in "\$sflag".*; do + echo + \$perli "\${args[@]}" -s "\$s" "\$@" + \$perli "\${args[@]}" -s "\$s" "\$@"$tsts + rc=\$? + (( rv = rv ? rv : rc )) + done + exit \$rv EOF chmod 755 test.sh if test $cm = llvm; then @@ -1649,7 +2226,8 @@ elif test $cm = dragonegg; then else emitbc=-c fi -echo set -x >Rebuild.sh +echo ": # work around NeXTstep bug" >Rebuild.sh +echo set -x >>Rebuild.sh for file in $SRCS; do op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'` test -f $file || file=$srcdir/$file @@ -1666,18 +2244,14 @@ for file in $SRCS; do done case $cm in dragonegg|llvm) - echo "rm -f mksh.s" >>Rebuild.sh - echo "llvm-link -o - $objs | opt $optflags | llc -o mksh.s" >>Rebuild.sh - lobjs=mksh.s + echo "rm -f $tfn.s" >>Rebuild.sh + echo "llvm-link -o - $objs | opt $optflags | llc -o $tfn.s" >>Rebuild.sh + lobjs=$tfn.s ;; *) lobjs=$objs ;; esac -case $tcfn in -a.exe) mkshexe=mksh.exe ;; -*) mkshexe=mksh ;; -esac echo tcfn=$mkshexe >>Rebuild.sh echo "$CC $CFLAGS $LDFLAGS -o \$tcfn $lobjs $LIBS $ccpr" >>Rebuild.sh echo 'test -f $tcfn || exit 1; size $tcfn' >>Rebuild.sh @@ -1711,9 +2285,10 @@ LIBS= $LIBS # \$(CC) \$(CFLAGS) \$(CPPFLAGS) -c \$< # for all make variants: -#REGRESS_FLAGS= -v +#REGRESS_FLAGS= -f #regress: # ./test.sh \$(REGRESS_FLAGS) +check_categories=$check_categories # for BSD make only: #.PATH: $srcdir @@ -1751,33 +2326,111 @@ else fi case $cm in dragonegg|llvm) - rmf mksh.s - v "llvm-link -o - $objs | opt $optflags | llc -o mksh.s" + rmf $tfn.s + v "llvm-link -o - $objs | opt $optflags | llc -o $tfn.s" ;; esac tcfn=$mkshexe test $cm = combine || v "$CC $CFLAGS $LDFLAGS -o $tcfn $lobjs $LIBS $ccpr" test -f $tcfn || exit 1 -test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || \ - rmf mksh.cat1 +test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >$tfn.cat1" || \ + rmf $tfn.cat1 test 0 = $eq && v size $tcfn i=install test -f /usr/ucb/$i && i=/usr/ucb/$i test 1 = $eq && e=: $e $e Installing the shell: -$e "# $i -c -s -o root -g bin -m 555 mksh /bin/mksh" -$e "# grep -x /bin/mksh /etc/shells >/dev/null || echo /bin/mksh >>/etc/shells" +$e "# $i -c -s -o root -g bin -m 555 $tfn /bin/$tfn" +$e "# grep -x /bin/$tfn /etc/shells >/dev/null || echo /bin/$tfn >>/etc/shells" $e "# $i -c -o root -g bin -m 444 dot.mkshrc /usr/share/doc/mksh/examples/" $e $e Installing the manual: -if test -f mksh.cat1; then - $e "# $i -c -o root -g bin -m 444 mksh.cat1" \ - "/usr/share/man/cat1/mksh.0" +if test -f $tfn.cat1; then + $e "# $i -c -o root -g bin -m 444 $tfn.cat1" \ + "/usr/share/man/cat1/$tfn.0" $e or fi -$e "# $i -c -o root -g bin -m 444 mksh.1 /usr/share/man/man1/mksh.1" +$e "# $i -c -o root -g bin -m 444 mksh.1 /usr/share/man/man1/$tfn.1" $e $e Run the regression test suite: ./test.sh $e Please also read the sample file dot.mkshrc and the fine manual. exit 0 + +: <<'EOD' + +=== Environment used === + +==== build environment ==== +AWK default: awk +CC default: cc +CFLAGS if empty, defaults to -xO2 or +O2 + or -O3 -qstrict or -O2, per compiler +CPPFLAGS default empty +LDFLAGS default empty; added before sources +LDSTATIC set this to '-static'; default unset +LIBS default empty; added after sources + [Interix] default: -lcrypt (XXX still needed?) +NOWARN -Wno-error or similar +NROFF default: nroff +TARGET_OS default: $(uname -s || uname) +TARGET_OSREV [QNX] default: $(uname -r) + +==== feature selectors ==== +USE_PRINTF_BUILTIN 1 to include (unsupported) printf(1) as builtin +===== general format ===== +HAVE_STRLEN ac_test +HAVE_STRING_H ac_header +HAVE_CAN_FSTACKPROTECTORALL ac_flags + +==== cpp definitions ==== +DEBUG dont use in production, wants gcc, implies: +DEBUG_LEAKS enable freeing resources before exiting +MKSHRC_PATH "~/.mkshrc" (do not change) +MKSH_A4PB force use of arc4random_pushb +MKSH_ASSUME_UTF8 (0=disabled, 1=enabled; default: unset) +MKSH_BINSHREDUCED if */sh or */-sh, enable set -o sh +MKSH_CLRTOEOL_STRING "\033[K" +MKSH_CLS_STRING "\033[;H\033[J" +MKSH_CONSERVATIVE_FDS fd 0-9 for scripts, shell only up to 31 +MKSH_DEFAULT_EXECSHELL "/bin/sh" (do not change) +MKSH_DEFAULT_PROFILEDIR "/etc" (do not change) +MKSH_DEFAULT_TMPDIR "/tmp" (do not change) +MKSH_DISABLE_DEPRECATED disable code paths scheduled for later removal +MKSH_DISABLE_EXPERIMENTAL disable code not yet comfy for (LTS) snapshots +MKSH_DISABLE_TTY_WARNING shut up warning about ctty if OS cant be fixed +MKSH_DONT_EMIT_IDSTRING omit RCS IDs from binary +MKSH_GCC55009 DANGER! see http://www.mirbsd.org/mksh.htm#p41 +MKSH_MIDNIGHTBSD01ASH_COMPAT set -o sh: additional compatibility quirk +MKSH_NOPROSPECTOFWORK disable jobs, co-processes, etc. (do not use) +MKSH_NOPWNAM skip PAM calls, for -static on eglibc, Solaris +MKSH_NO_CMDLINE_EDITING disable command line editing code entirely +MKSH_NO_DEPRECATED_WARNING omit warning when deprecated stuff is run +MKSH_NO_EXTERNAL_CAT omit hack to skip cat builtin when flags passed +MKSH_NO_LIMITS omit ulimit code +MKSH_NO_SIGSETJMP define if sigsetjmp is broken or not available +MKSH_NO_SIGSUSPEND use sigprocmask+pause instead of sigsuspend +MKSH_SMALL omit some code, optimise hard for size (slower) +MKSH_SMALL_BUT_FAST disable some hard-for-size optim. (modern sys.) +MKSH_S_NOVI=1 disable Vi editing mode (default if MKSH_SMALL) +MKSH_TYPEDEF_SIG_ATOMIC_T define to e.g. 'int' if sig_atomic_t is missing +MKSH_TYPEDEF_SSIZE_T define to e.g. 'long' if your OS has no ssize_t +MKSH_UNEMPLOYED disable job control (but not jobs/co-processes) + +=== generic installation instructions === + +Set CC and possibly CFLAGS, CPPFLAGS, LDFLAGS, LIBS. If cross-compiling, +also set TARGET_OS. To disable tests, set e.g. HAVE_STRLCPY=0; to enable +them, set to a value other than 0 or 1. Ensure /bin/ed is installed. For +MKSH_SMALL but with Vi mode, add -DMKSH_S_NOVI=0 to CPPFLAGS as well. + +Normally, the following command is what you want to run, then: +$ (sh Build.sh -r -c lto && ./test.sh -f) 2>&1 | tee log + +Copy dot.mkshrc to /etc/skel/.mkshrc; install mksh into $prefix/bin; or +/bin; install the manpage, if omitting the -r flag a catmanpage is made +using $NROFF. Consider using a forward script as /etc/skel/.mkshrc like +https://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/deb/mksh/debian/.mkshrc?rev=HEAD +and put dot.mkshrc as /etc/mkshrc so users need not keep up their HOME. + +EOD diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 03ca8bf..0000000 --- a/src/Makefile +++ /dev/null @@ -1,97 +0,0 @@ -# $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 -# -# 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 - -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 diff --git a/src/check.pl b/src/check.pl index 7dfab36..5e55cd4 100644 --- a/src/check.pl +++ b/src/check.pl @@ -1,7 +1,7 @@ -# $MirOS: src/bin/mksh/check.pl,v 1.27 2011/05/29 02:18:47 tg Exp $ +# $MirOS: src/bin/mksh/check.pl,v 1.31 2012/04/06 12:22:14 tg Exp $ # $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $ #- -# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 # Thorsten Glaser # # Provided that these terms and disclaimer and all copyright notices @@ -75,6 +75,7 @@ # USER # (values taken from the environment of # the test harness). +# CYGWIN is set to nodosfilewarning. # ENV is set to /nonexistant. # __progname is set to the -p argument. # __perlname is set to $^X (perlexe). @@ -150,7 +151,24 @@ # p tag takes parameters (used with m). # s tag can be used several times. -use POSIX qw(EINTR); +# pull EINTR from POSIX.pm or Errno.pm if they exist +# otherwise just skip it +BEGIN { + $EINTR = 0; + eval { + require POSIX; + $EINTR = POSIX::EINTR(); + }; + if ($@) { + eval { + require Errno; + $EINTR = Errno::EINTR(); + } or do { + $EINTR = 0; + }; + } +}; + use Getopt::Std; use Config; @@ -262,6 +280,7 @@ foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME', 'PATH', 'SHELL', 'UNIXMODE', 'USER')) { $new_env{$env} = $ENV{$env} if defined $ENV{$env}; } +$new_env{'CYGWIN'} = 'nodosfilewarning'; $new_env{'ENV'} = '/nonexistant'; if (($os eq 'VMS') || ($Config{perlpath} =~ m/$Config{_exe}$/i)) { $new_env{'__perlname'} = $Config{perlpath}; @@ -555,7 +574,9 @@ run_test $xpid = waitpid($pid, 0); $child_kill_ok = 0; if ($xpid < 0) { - next if $! == EINTR; + if ($EINTR) { + next if $! == $EINTR; + } print STDERR "$prog: error waiting for child - $!\n"; return undef; } diff --git a/src/check.t b/src/check.t index 8df403a..d33febd 100644 --- a/src/check.t +++ b/src/check.t @@ -1,9 +1,10 @@ -# $MirOS: src/bin/mksh/check.t,v 1.483 2011/10/07 19:51:42 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.597 2013/02/19 18:45:17 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, 2011 +# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013 # Thorsten Glaser # # Provided that these terms and disclaimer and all copyright notices @@ -23,14 +24,27 @@ #- # You may also want to test IFS with the script at # http://www.research.att.com/~gsf/public/ifs.sh +# +# More testsuites at: +# http://www.freebsd.org/cgi/cvsweb.cgi/src/tools/regression/bin/test/regress.sh?rev=HEAD expected-stdout: - @(#)MIRBSD KSH R40 2011/10/07 + @(#)MIRBSD KSH R43 2013/02/19 description: Check version of shell. stdin: echo $KSH_VERSION name: KSH_VERSION +category: shell:legacy-no +--- +expected-stdout: + @(#)LEGACY KSH R43 2013/02/19 +description: + Check version of legacy shell. +stdin: + echo $KSH_VERSION +name: KSH_VERSION-legacy +category: shell:legacy-yes --- name: selftest-1 description: @@ -65,12 +79,29 @@ category: disabled stdin: set --- +name: selftest-legacy +description: + Check some things in the LEGACY KSH +category: shell:legacy-yes +stdin: + set +o emacs + set +o vi + [[ "$(set +o) -o" = *"-o emacs -o"* ]] && echo 1=emacs + [[ "$(set +o) -o" = *"-o vi -o"* ]] && echo 1=vi + set -o emacs + set -o vi + [[ "$(set +o) -o" = *"-o emacs -o"* ]] && echo 2=emacs + [[ "$(set +o) -o" = *"-o vi -o"* ]] && echo 2=vi +expected-stdout: + 2=emacs + 2=vi +--- name: selftest-direct-builtin-call description: Check that direct builtin calls work stdin: - ln -s "$__progname" cat - ln -s "$__progname" echo + ln -s "$__progname" cat || cp "$__progname" cat + ln -s "$__progname" echo || cp "$__progname" echo ./echo -c 'echo foo' | ./cat -u expected-stdout: -c echo foo @@ -186,14 +217,21 @@ description: the time-limit clause (e.g. to 7) if this occurs. time-limit: 3 stdin: - echo -n >tf - alias ls=ls - ls - echo $(ls) + print '#!'"$__progname"'\necho tf' >lq + chmod +x lq + PATH=$PWD:$PATH + alias lq=lq + lq + echo = now + i=`lq` + print -r -- $i + echo = out exit 0 expected-stdout: tf + = now tf + = out --- name: alias-10 description: @@ -279,6 +317,39 @@ stdin: expected-stdout: 5 --- +name: arith-div-byzero +description: + Check division by zero errors out +stdin: + x=$(echo $((1 / 0))) + echo =$?:$x. +expected-stdout: + =1:. +expected-stderr-pattern: + /.*divisor/ +--- +name: arith-div-intmin-by-minusone +description: + Check division overflow wraps around silently +category: int:32 +stdin: + echo signed:$((-2147483648 / -1))r$((-2147483648 % -1)). + echo unsigned:$((# -2147483648 / -1))r$((# -2147483648 % -1)). +expected-stdout: + signed:-2147483648r0. + unsigned:0r2147483648. +--- +name: arith-div-intmin-by-minusone-64 +description: + Check division overflow wraps around silently +category: int:64 +stdin: + echo signed:$((-9223372036854775808 / -1))r$((-9223372036854775808 % -1)). + echo unsigned:$((# -9223372036854775808 / -1))r$((# -9223372036854775808 % -1)). +expected-stdout: + signed:-9223372036854775808r0. + unsigned:0r9223372036854775808. +--- name: arith-assop-assoc-1 description: Check associativity of assignment-operator operator @@ -290,9 +361,46 @@ expected-stdout: 6 6,5,3 --- +name: arith-mandatory +description: + If MKSH_GCC55009 is set when compiling, passing of + this test is *mandatory* for a valid mksh executable! +category: shell:legacy-no +stdin: + typeset -i sari=0 + typeset -Ui uari=0 + typeset -i x=0 + print -r -- $((x++)):$sari=$uari. + let --sari --uari + print -r -- $((x++)):$sari=$uari. + sari=2147483647 uari=2147483647 + print -r -- $((x++)):$sari=$uari. + let ++sari ++uari + print -r -- $((x++)):$sari=$uari. + let --sari --uari + let 'sari *= 2' 'uari *= 2' + let ++sari ++uari + print -r -- $((x++)):$sari=$uari. + let ++sari ++uari + print -r -- $((x++)):$sari=$uari. + sari=-2147483648 uari=-2147483648 + print -r -- $((x++)):$sari=$uari. + let --sari --uari + print -r -- $((x++)):$sari=$uari. +expected-stdout: + 0:0=0. + 1:-1=4294967295. + 2:2147483647=2147483647. + 3:-2147483648=2147483648. + 4:-1=4294967295. + 5:0=0. + 6:-2147483648=2147483648. + 7:2147483647=2147483647. +--- name: arith-unsigned-1 description: Check if unsigned arithmetics work +category: int:32 stdin: # signed vs unsigned echo x1 $((-1)) $((#-1)) @@ -330,6 +438,7 @@ expected-stdout: name: arith-limit32-1 description: Check if arithmetics are 32 bit +category: int:32 stdin: # signed vs unsigned echo x1 $((-1)) $((#-1)) @@ -354,6 +463,34 @@ expected-stdout: x5 0 0 x6 1 1 --- +name: arith-limit64-1 +description: + Check if arithmetics are 64 bit +category: int:64 +stdin: + # signed vs unsigned + echo x1 $((-1)) $((#-1)) + # calculating + typeset -i vs + typeset -Ui vu + vs=9223372036854775807; vu=9223372036854775807 + echo x2 $vs $vu + let vs++ vu++ + echo x3 $vs $vu + vs=18446744073709551615; vu=18446744073709551615 + echo x4 $vs $vu + let vs++ vu++ + echo x5 $vs $vu + let vs++ vu++ + echo x6 $vs $vu +expected-stdout: + x1 -1 18446744073709551615 + x2 9223372036854775807 9223372036854775807 + x3 -9223372036854775808 9223372036854775808 + x4 -1 18446744073709551615 + x5 0 0 + x6 1 1 +--- name: bksl-nl-ign-1 description: Check that \newline is not collapsed after # @@ -1010,7 +1147,9 @@ description: Check package for cd -Pe need-pass: no # the mv command fails on Cygwin -category: !os:cygwin +# Hurd aborts the testsuite (permission denied) +# QNX does not find subdir to cd into +category: !os:cygwin,!os:gnu,!os:msys,!os:nto,!nosymlink file-setup: file 644 "x" mkdir noread noread/target noread/target/subdir ln -s noread link @@ -1065,49 +1204,52 @@ name: expand-ugly description: Check that weird ${foo+bar} constructs are parsed correctly stdin: - (echo 1 ${IFS+'}'z}) 2>&- || echo failed in 1 - (echo 2 "${IFS+'}'z}") 2>&- || echo failed in 2 - (echo 3 "foo ${IFS+'bar} baz") 2>&- || echo failed in 3 - (echo -n '4 '; printf '%s\n' "foo ${IFS+"b c"} baz") 2>&- || echo failed in 4 - (echo -n '5 '; printf '%s\n' "foo ${IFS+b c} baz") 2>&- || echo failed in 5 - (echo 6 ${IFS+"}"z}) 2>&- || echo failed in 6 - (echo 7 "${IFS+"}"z}") 2>&- || echo failed in 7 - (echo 8 "${IFS+\"}\"z}") 2>&- || echo failed in 8 - (echo 9 "${IFS+\"\}\"z}") 2>&- || echo failed in 9 - (echo 10 foo ${IFS+'bar} baz'}) 2>&- || echo failed in 10 - (echo 11 "$(echo "${IFS+'}'z}")") 2>&- || echo failed in 11 - (echo 12 "$(echo ${IFS+'}'z})") 2>&- || echo failed in 12 - (echo 13 ${IFS+\}z}) 2>&- || echo failed in 13 - (echo 14 "${IFS+\}z}") 2>&- || echo failed in 14 - u=x; (echo -n '15 '; printf '<%s> ' "foo ${IFS+a"b$u{ {"{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz; echo .) 2>&- || echo failed in 15 - l=t; (echo 16 ${IFS+h`echo -n i ${IFS+$l}h`ere}) 2>&- || echo failed in 16 - l=t; (echo 17 ${IFS+h$(echo -n i ${IFS+$l}h)ere}) 2>&- || echo failed in 17 - l=t; (echo 18 "${IFS+h`echo -n i ${IFS+$l}h`ere}") 2>&- || echo failed in 18 - l=t; (echo 19 "${IFS+h$(echo -n i ${IFS+$l}h)ere}") 2>&- || echo failed in 19 - l=t; (echo 20 ${IFS+h`echo -n i "${IFS+$l}"h`ere}) 2>&- || echo failed in 20 - l=t; (echo 21 ${IFS+h$(echo -n i "${IFS+$l}"h)ere}) 2>&- || echo failed in 21 - l=t; (echo 22 "${IFS+h`echo -n i "${IFS+$l}"h`ere}") 2>&- || echo failed in 22 - l=t; (echo 23 "${IFS+h$(echo -n i "${IFS+$l}"h)ere}") 2>&- || echo failed in 23 - key=value; (echo -n '24 '; printf '%s\n' "${IFS+'$key'}") 2>&- || echo failed in 24 - key=value; (echo -n '25 '; printf '%s\n' "${IFS+"'$key'"}") 2>&- || echo failed in 25 # ksh93: “'$key'” - key=value; (echo -n '26 '; printf '%s\n' ${IFS+'$key'}) 2>&- || echo failed in 26 - key=value; (echo -n '27 '; printf '%s\n' ${IFS+"'$key'"}) 2>&- || echo failed in 27 - (echo -n '28 '; printf '%s\n' "${IFS+"'"x ~ x'}'x"'}"x}" #') 2>&- || echo failed in 28 - u=x; (echo -n '29 '; printf '<%s> ' foo ${IFS+a"b$u{ {"{ {\}b} c ${IFS+d{}} bar ${IFS-e{}} baz; echo .) 2>&- || echo failed in 29 - (echo -n '30 '; printf '<%s> ' ${IFS+foo 'b\ - ar' baz}; echo .) 2>&- || (echo failed in 30; echo failed in 31) - (echo -n '32 '; printf '<%s> ' ${IFS+foo "b\ - ar" baz}; echo .) 2>&- || echo failed in 32 - (echo -n '33 '; printf '<%s> ' "${IFS+foo 'b\ - ar' baz}"; echo .) 2>&- || echo failed in 33 - (echo -n '34 '; printf '<%s> ' "${IFS+foo "b\ - ar" baz}"; echo .) 2>&- || echo failed in 34 - (echo -n '35 '; printf '<%s> ' ${v=a\ b} x ${v=c\ d}; echo .) 2>&- || echo failed in 35 - (echo -n '36 '; printf '<%s> ' "${v=a\ b}" x "${v=c\ d}"; echo .) 2>&- || echo failed in 36 - (echo -n '37 '; printf '<%s> ' ${v-a\ b} x ${v-c\ d}; echo .) 2>&- || echo failed in 37 - (echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>&- || echo failed in 38 - foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>&- || echo failed in 39 - foo="a b c"; (echo -n '40 '; printf '<%s> ' "${foo#a}"; echo .) 2>&- || echo failed in 40 + print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn + print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "<$x> "; done' >pfs + chmod +x pfn pfs + (echo 1 ${IFS+'}'z}) 2>/dev/null || echo failed in 1 + (echo 2 "${IFS+'}'z}") 2>/dev/null || echo failed in 2 + (echo 3 "foo ${IFS+'bar} baz") 2>/dev/null || echo failed in 3 + (echo -n '4 '; ./pfn "foo ${IFS+"b c"} baz") 2>/dev/null || echo failed in 4 + (echo -n '5 '; ./pfn "foo ${IFS+b c} baz") 2>/dev/null || echo failed in 5 + (echo 6 ${IFS+"}"z}) 2>/dev/null || echo failed in 6 + (echo 7 "${IFS+"}"z}") 2>/dev/null || echo failed in 7 + (echo 8 "${IFS+\"}\"z}") 2>/dev/null || echo failed in 8 + (echo 9 "${IFS+\"\}\"z}") 2>/dev/null || echo failed in 9 + (echo 10 foo ${IFS+'bar} baz'}) 2>/dev/null || echo failed in 10 + (echo 11 "$(echo "${IFS+'}'z}")") 2>/dev/null || echo failed in 11 + (echo 12 "$(echo ${IFS+'}'z})") 2>/dev/null || echo failed in 12 + (echo 13 ${IFS+\}z}) 2>/dev/null || echo failed in 13 + (echo 14 "${IFS+\}z}") 2>/dev/null || echo failed in 14 + u=x; (echo -n '15 '; ./pfs "foo ${IFS+a"b$u{ {"{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz; echo .) 2>/dev/null || echo failed in 15 + l=t; (echo 16 ${IFS+h`echo -n i ${IFS+$l}h`ere}) 2>/dev/null || echo failed in 16 + l=t; (echo 17 ${IFS+h$(echo -n i ${IFS+$l}h)ere}) 2>/dev/null || echo failed in 17 + l=t; (echo 18 "${IFS+h`echo -n i ${IFS+$l}h`ere}") 2>/dev/null || echo failed in 18 + l=t; (echo 19 "${IFS+h$(echo -n i ${IFS+$l}h)ere}") 2>/dev/null || echo failed in 19 + l=t; (echo 20 ${IFS+h`echo -n i "${IFS+$l}"h`ere}) 2>/dev/null || echo failed in 20 + l=t; (echo 21 ${IFS+h$(echo -n i "${IFS+$l}"h)ere}) 2>/dev/null || echo failed in 21 + l=t; (echo 22 "${IFS+h`echo -n i "${IFS+$l}"h`ere}") 2>/dev/null || echo failed in 22 + l=t; (echo 23 "${IFS+h$(echo -n i "${IFS+$l}"h)ere}") 2>/dev/null || echo failed in 23 + key=value; (echo -n '24 '; ./pfn "${IFS+'$key'}") 2>/dev/null || echo failed in 24 + key=value; (echo -n '25 '; ./pfn "${IFS+"'$key'"}") 2>/dev/null || echo failed in 25 # ksh93: “'$key'” + key=value; (echo -n '26 '; ./pfn ${IFS+'$key'}) 2>/dev/null || echo failed in 26 + key=value; (echo -n '27 '; ./pfn ${IFS+"'$key'"}) 2>/dev/null || echo failed in 27 + (echo -n '28 '; ./pfn "${IFS+"'"x ~ x'}'x"'}"x}" #') 2>/dev/null || echo failed in 28 + u=x; (echo -n '29 '; ./pfs foo ${IFS+a"b$u{ {"{ {\}b} c ${IFS+d{}} bar ${IFS-e{}} baz; echo .) 2>/dev/null || echo failed in 29 + (echo -n '30 '; ./pfs ${IFS+foo 'b\ + ar' baz}; echo .) 2>/dev/null || (echo failed in 30; echo failed in 31) + (echo -n '32 '; ./pfs ${IFS+foo "b\ + ar" baz}; echo .) 2>/dev/null || echo failed in 32 + (echo -n '33 '; ./pfs "${IFS+foo 'b\ + ar' baz}"; echo .) 2>/dev/null || echo failed in 33 + (echo -n '34 '; ./pfs "${IFS+foo "b\ + ar" baz}"; echo .) 2>/dev/null || echo failed in 34 + (echo -n '35 '; ./pfs ${v=a\ b} x ${v=c\ d}; echo .) 2>/dev/null || echo failed in 35 + (echo -n '36 '; ./pfs "${v=a\ b}" x "${v=c\ d}"; echo .) 2>/dev/null || echo failed in 36 + (echo -n '37 '; ./pfs ${v-a\ b} x ${v-c\ d}; echo .) 2>/dev/null || echo failed in 37 + (echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>/dev/null || echo failed in 38 + foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>/dev/null || echo failed in 39 + foo="a b c"; (echo -n '40 '; ./pfs "${foo#a}"; echo .) 2>/dev/null || echo failed in 40 expected-stdout: 1 }z 2 ''z} @@ -1161,12 +1303,12 @@ stdin: (echo "$1 plus norm foo ${v+'bar'} baz") (echo "$1 dash norm foo ${v-'bar'} baz") (echo "$1 eqal norm foo ${v='bar'} baz") - (echo "$1 qstn norm foo ${v?'bar'} baz") 2>&- || \ + (echo "$1 qstn norm foo ${v?'bar'} baz") 2>/dev/null || \ echo "$1 qstn norm -> error" (echo "$1 PLUS norm foo ${v:+'bar'} baz") (echo "$1 DASH norm foo ${v:-'bar'} baz") (echo "$1 EQAL norm foo ${v:='bar'} baz") - (echo "$1 QSTN norm foo ${v:?'bar'} baz") 2>&- || \ + (echo "$1 QSTN norm foo ${v:?'bar'} baz") 2>/dev/null || \ echo "$1 QSTN norm -> error" } tl_paren() { @@ -1175,12 +1317,12 @@ stdin: (echo "$1 plus parn foo ${v+(bar)} baz") (echo "$1 dash parn foo ${v-(bar)} baz") (echo "$1 eqal parn foo ${v=(bar)} baz") - (echo "$1 qstn parn foo ${v?(bar)} baz") 2>&- || \ + (echo "$1 qstn parn foo ${v?(bar)} baz") 2>/dev/null || \ echo "$1 qstn parn -> error" (echo "$1 PLUS parn foo ${v:+(bar)} baz") (echo "$1 DASH parn foo ${v:-(bar)} baz") (echo "$1 EQAL parn foo ${v:=(bar)} baz") - (echo "$1 QSTN parn foo ${v:?(bar)} baz") 2>&- || \ + (echo "$1 QSTN parn foo ${v:?(bar)} baz") 2>/dev/null || \ echo "$1 QSTN parn -> error" } tl_brace() { @@ -1189,12 +1331,12 @@ stdin: (echo "$1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz") (echo "$1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz") (echo "$1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz") - (echo "$1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz") 2>&- || \ + (echo "$1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz") 2>/dev/null || \ echo "$1 qstn brac -> error" (echo "$1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz") (echo "$1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz") (echo "$1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz") - (echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>&- || \ + (echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>/dev/null || \ echo "$1 QSTN brac -> error" } tl_norm 1 - @@ -1291,12 +1433,12 @@ stdin: (echo $1 plus norm foo ${v+'bar'} baz) (echo $1 dash norm foo ${v-'bar'} baz) (echo $1 eqal norm foo ${v='bar'} baz) - (echo $1 qstn norm foo ${v?'bar'} baz) 2>&- || \ + (echo $1 qstn norm foo ${v?'bar'} baz) 2>/dev/null || \ echo "$1 qstn norm -> error" (echo $1 PLUS norm foo ${v:+'bar'} baz) (echo $1 DASH norm foo ${v:-'bar'} baz) (echo $1 EQAL norm foo ${v:='bar'} baz) - (echo $1 QSTN norm foo ${v:?'bar'} baz) 2>&- || \ + (echo $1 QSTN norm foo ${v:?'bar'} baz) 2>/dev/null || \ echo "$1 QSTN norm -> error" } tl_paren() { @@ -1305,12 +1447,12 @@ stdin: (echo $1 plus parn foo ${v+\(bar')'} baz) (echo $1 dash parn foo ${v-\(bar')'} baz) (echo $1 eqal parn foo ${v=\(bar')'} baz) - (echo $1 qstn parn foo ${v?\(bar')'} baz) 2>&- || \ + (echo $1 qstn parn foo ${v?\(bar')'} baz) 2>/dev/null || \ echo "$1 qstn parn -> error" (echo $1 PLUS parn foo ${v:+\(bar')'} baz) (echo $1 DASH parn foo ${v:-\(bar')'} baz) (echo $1 EQAL parn foo ${v:=\(bar')'} baz) - (echo $1 QSTN parn foo ${v:?\(bar')'} baz) 2>&- || \ + (echo $1 QSTN parn foo ${v:?\(bar')'} baz) 2>/dev/null || \ echo "$1 QSTN parn -> error" } tl_brace() { @@ -1319,12 +1461,12 @@ stdin: (echo $1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz) (echo $1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz) (echo $1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz) - (echo $1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz) 2>&- || \ + (echo $1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz) 2>/dev/null || \ echo "$1 qstn brac -> error" (echo $1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz) (echo $1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz) (echo $1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz) - (echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>&- || \ + (echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>/dev/null || \ echo "$1 QSTN brac -> error" } tl_norm 1 - @@ -1460,6 +1602,19 @@ expected-stdout: 0 = 1 . 111 = 3 . --- +name: expand-weird-3 +description: + Check that trimming works with positional parameters (Debian #48453) +stdin: + A=9999-02 + B=9999 + echo 1=${A#$B?}. + set -- $A $B + echo 2=${1#$2?}. +expected-stdout: + 1=02. + 2=02. +--- name: eglob-bad-1 description: Check that globbing isn't done when glob has syntax error @@ -1864,7 +2019,9 @@ name: glob-bad-2 description: Check that symbolic links aren't stat()'d # breaks on FreeMiNT (cannot unlink dangling symlinks) -category: !os:mint +# breaks on MSYS (does not support symlinks) +# breaks on Dell UNIX 4.0 R2.2 (SVR4) where unlink also fails +category: !os:mint,!os:msys,!os:svr4.0,!nosymlink file-setup: dir 755 "dir" file-setup: symlink 644 "dir/abc" non-existent-file @@ -1911,7 +2068,9 @@ description: Check that globbing matches the right things... # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition) # breaks on Cygwin 1.7 (files are now UTF-16 or something) -category: !os:cygwin,!os:darwin +# breaks on QNX 6.4.1 (says RT) +category: !os:cygwin,!os:darwin,!os:msys,!os:nto +need-pass: no file-setup: file 644 "ac" stdin: echo a[-]* @@ -2090,6 +2249,7 @@ stdin: tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<'$bar' tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<\$bar tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<-foo + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"$(echo "foo bar")" expected-stdout: sbb sbb @@ -2098,6 +2258,7 @@ expected-stdout: $one $one -sbb + sbb one --- name: heredoc-9b description: @@ -2135,7 +2296,7 @@ name: heredoc-9e description: Check here string related regression with multiple iops stdin: - echo $(tr r z <<<'bar' 2>&-) + echo $(tr r z <<<'bar' 2>/dev/null) expected-stdout: baz --- @@ -2155,7 +2316,13 @@ stdin: =c $x \x40= EOF } - typeset -f foo + fnd=$(typeset -f foo) + print -r -- "$fnd" + function foo { + echo blub + } + foo + eval "$fnd" foo # rather nonsensical, but… vd=<<<"=d $x \x40=" @@ -2165,11 +2332,12 @@ stdin: print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |" expected-stdout: function foo { - vc= <<-EOF + vc=<<-EOF =c $x \x40= EOF } + blub | va={=a u \x40= } vb={=b $x \x40= } vc={=c u \x40= @@ -2197,20 +2365,27 @@ stdin: =d $x \x40= } - typeset -f foo + fnd=$(typeset -f foo) + print -r -- "$fnd" + function foo { + echo blub + } + foo + eval "$fnd" foo print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |" expected-stdout: function foo { - vc= <<- + vc=<<- =c $x \x40= << - vd= <<-"" + vd=<<-"" =d $x \x40= } + blub | va={=a u \x40= } vb={=b $x \x40= } vc={=c u \x40= @@ -3595,6 +3770,149 @@ expected-stdout: 64 64 --- +name: integer-base-check-flat +description: + Check behaviour does not match POSuX, because a not type-safe + scripting language has *no* business interpreting "010" as octal +category: shell:legacy-no +stdin: + echo :$((10)).$((010)).$((0x10)). +expected-stdout: + :10.10.16. +--- +name: integer-base-check-flat-legacy +description: + Check behaviour matches POSuX for LEGACY KSH +category: shell:legacy-yes +stdin: + echo :$((10)).$((010)).$((0x10)). +expected-stdout: + :10.8.16. +--- +name: integer-base-check-numeric-from +description: + Check behaviour for base one to 36, and that 37 errors out +stdin: + echo 1:$((1#1))0. + i=1 + while (( ++i <= 36 )); do + eval 'echo '$i':$(('$i'#10)).' + done + echo 37:$($__progname -c 'echo $((37#10))').$?: +expected-stdout: + 1:490. + 2:2. + 3:3. + 4:4. + 5:5. + 6:6. + 7:7. + 8:8. + 9:9. + 10:10. + 11:11. + 12:12. + 13:13. + 14:14. + 15:15. + 16:16. + 17:17. + 18:18. + 19:19. + 20:20. + 21:21. + 22:22. + 23:23. + 24:24. + 25:25. + 26:26. + 27:27. + 28:28. + 29:29. + 30:30. + 31:31. + 32:32. + 33:33. + 34:34. + 35:35. + 36:36. + 37:.0: +expected-stderr-pattern: + /.*bad number '37#10'/ +--- +name: integer-base-check-numeric-to +description: + Check behaviour for base one to 36, and that 37 errors out +stdin: + i=0 + while (( ++i <= 37 )); do + typeset -Uui$i x=0x40 + eval "typeset -i10 y=$x" + print $i:$x.$y. + done +expected-stdout: + 1:1#@.64. + 2:2#1000000.64. + 3:3#2101.64. + 4:4#1000.64. + 5:5#224.64. + 6:6#144.64. + 7:7#121.64. + 8:8#100.64. + 9:9#71.64. + 10:64.64. + 11:11#59.64. + 12:12#54.64. + 13:13#4C.64. + 14:14#48.64. + 15:15#44.64. + 16:16#40.64. + 17:17#3D.64. + 18:18#3A.64. + 19:19#37.64. + 20:20#34.64. + 21:21#31.64. + 22:22#2K.64. + 23:23#2I.64. + 24:24#2G.64. + 25:25#2E.64. + 26:26#2C.64. + 27:27#2A.64. + 28:28#28.64. + 29:29#26.64. + 30:30#24.64. + 31:31#22.64. + 32:32#20.64. + 33:33#1V.64. + 34:34#1U.64. + 35:35#1T.64. + 36:36#1S.64. + 37:36#1S.64. +expected-stderr-pattern: + /.*bad integer base: 37/ +--- +name: integer-arithmetic-span +description: + Check wraparound and size that is defined in mksh +category: int:32 +stdin: + echo s:$((2147483647+1)).$(((2147483647*2)+1)).$(((2147483647*2)+2)). + echo u:$((#2147483647+1)).$((#(2147483647*2)+1)).$((#(2147483647*2)+2)). +expected-stdout: + s:-2147483648.-1.0. + u:2147483648.4294967295.0. +--- +name: integer-arithmetic-span-64 +description: + Check wraparound and size that is defined in mksh +category: int:64 +stdin: + echo s:$((9223372036854775807+1)).$(((9223372036854775807*2)+1)).$(((9223372036854775807*2)+2)). + echo u:$((#9223372036854775807+1)).$((#(9223372036854775807*2)+1)).$((#(9223372036854775807*2)+2)). +expected-stdout: + s:-9223372036854775808.-1.0. + u:9223372036854775808.18446744073709551615.0. +--- name: lineno-stdin description: See if $LINENO is updated and can be modified. @@ -3723,7 +4041,7 @@ stdin: echo =1 trap "echo trap 2 executed" UNKNOWNSIGNAL EXIT 999999 FNORD echo = $? - ) 2>&1 | sed "s^${__progname}: \[[0-9]*]PROG" + ) 2>&1 | sed "s^${__progname%.exe}\.*e*x*e*: \[[0-9]*]PROG" expected-stdout: PROG: trap: bad signal 'UNKNOWNSIGNAL' foo @@ -3781,7 +4099,7 @@ description: Check read with delimiters stdin: emit() { - printf 'foo bar\tbaz\nblah \0blub\tblech\nmyok meck \0' + print -n '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 @@ -3887,9 +4205,11 @@ description: (so quotes could also be used to hide hem). The former is easier, the later better... stdin: - echo $(echo \() + echo $(echo \( ) + echo $(echo "(" ) expected-stdout: ( + ( --- name: regression-9 description: @@ -3926,6 +4246,7 @@ description: but not 0 according to the getopt(1) manual page, ksh88, and Bourne sh (such as /bin/sh on Solaris). In mksh R39b, we honour POSIX except when -o sh is set. +category: shell:legacy-no stdin: showf() { [[ -o posix ]]; FPOSIX=$((1-$?)) @@ -3949,6 +4270,38 @@ expected-stdout: FPOSIX=0 FSH=1 rv=1 FPOSIX=1 FSH=0 rv=0 --- +name: regression-10-legacy +description: + The following: + set -- `false` + echo $? + should print 0 according to POSIX (dash, bash, ksh93, posh) + but not 0 according to the getopt(1) manual page, ksh88, and + Bourne sh (such as /bin/sh on Solaris). +category: shell:legacy-yes +stdin: + showf() { + [[ -o posix ]]; FPOSIX=$((1-$?)) + [[ -o sh ]]; FSH=$((1-$?)) + echo -n "FPOSIX=$FPOSIX FSH=$FSH " + } + set +o posix +o sh + showf + set -- `false` + echo rv=$? + set -o sh + showf + set -- `false` + echo rv=$? + set -o posix + showf + set -- `false` + echo rv=$? +expected-stdout: + FPOSIX=0 FSH=0 rv=1 + FPOSIX=0 FSH=1 rv=1 + FPOSIX=1 FSH=0 rv=1 +--- name: regression-11 description: The following: @@ -3994,7 +4347,7 @@ stdin: cat t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 > t2 cat t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 > t1 cat t1 t1 t1 t1 > t2 - (: ; cat t2 2>&-) | sleep 1 + (: ; cat t2 2>/dev/null) | sleep 1 --- name: regression-14 description: @@ -4346,12 +4699,15 @@ name: regression-42 description: Can't use command line assignments to assign readonly parameters. stdin: + print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \ + 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \ + done >env; chmod +x env; PATH=.:$PATH foo=bar readonly foo foo=stuff env | grep '^foo' expected-exit: e != 0 expected-stderr-pattern: - /.*read *only.*/ + /read-only/ --- name: regression-43 description: @@ -4461,10 +4817,10 @@ name: regression-50 description: Check that aliases do not use continuation prompt after trailing semi-colon. -file-setup: file 644 "env" +file-setup: file 644 "envf" PS1=Y PS2=X -env-setup: !ENV=./env! +env-setup: !ENV=./envf! need-ctty: yes arguments: !-i! stdin: @@ -4491,11 +4847,11 @@ expected-stdout: name: regression-52 description: Check that globbing works in pipelined commands -file-setup: file 644 "env" +file-setup: file 644 "envf" PS1=P file-setup: file 644 "abc" stuff -env-setup: !ENV=./env! +env-setup: !ENV=./envf! need-ctty: yes arguments: !-i! stdin: @@ -4666,7 +5022,8 @@ expected-stdout: M xxx[1]=7 N - typeset -i xxx + set -A xxx + typeset -i xxx[1] --- name: regression-58 description: @@ -4767,6 +5124,9 @@ stdin: typeset - echo FNORD-8 } | fgrep FNORD + fnord=(42 23) + typeset -p fnord + echo FNORD-9 expected-stdout: FNORD-0 FNORD-1 @@ -4817,6 +5177,10 @@ expected-stdout: FNORD_G=7 FNORD_H=8 FNORD-8 + set -A fnord + typeset fnord[0]=42 + typeset fnord[1]=23 + FNORD-9 --- name: regression-64 description: @@ -4843,6 +5207,30 @@ stdin: expected-stdout: ok --- +name: regression-66 +description: + Check that quoting is sane +category: !nojsig +stdin: + ac_space=' ' + ac_newline=' + ' + set | grep ^ac_ |& + set -A lines + while IFS= read -pr line; do + if [[ $line = *space* ]]; then + lines[0]=$line + else + lines[1]=$line + fi + done + for line in "${lines[@]}"; do + print -r -- "$line" + done +expected-stdout: + ac_space=' ' + ac_newline=$'\n' +--- name: readonly-0 description: Ensure readonly is honoured for assignments and unset @@ -4859,7 +5247,7 @@ expected-stdout: = 1 x . expected-stderr-pattern: - /read *only/ + /read-only/ --- name: readonly-1 description: @@ -4869,7 +5257,7 @@ stdin: expected-stdout: aborted, 2 expected-stderr-pattern: - /read *only/ + /read-only/ --- name: readonly-2a description: @@ -4888,7 +5276,7 @@ stdin: expected-stdout: 2 . expected-stderr-pattern: - /read *only/ + /read-only/ --- name: readonly-3 description: @@ -4900,7 +5288,7 @@ expected-stdout: 0 x . 2 . expected-stderr-pattern: - /read *only/ + /read-only/ --- name: syntax-1 description: @@ -5005,6 +5393,9 @@ name: xxx-exec-environment-1 description: Check to see if exec sets it's environment correctly stdin: + print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \ + 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \ + done >env; chmod +x env; PATH=.:$PATH FOO=bar exec env expected-stdout-pattern: /(^|.*\n)FOO=bar\n/ @@ -5014,9 +5405,11 @@ description: Check to make sure exec doesn't change environment if a program isn't exec-ed stdin: - sortprog=$(whence -p sort) || sortprog=cat - env | $sortprog | grep -v '^RANDOM=' >bar1 - FOO=bar exec; env | $sortprog | grep -v '^RANDOM=' >bar2 + print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \ + 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \ + done >env; chmod +x env; PATH=.:$PATH + env >bar1 + FOO=bar exec; env >bar2 cmp -s bar1 bar2 --- name: exec-function-environment-1 @@ -5227,15 +5620,38 @@ env-setup: !HOME=/sweet! stdin: echo ${A=a=}~ b=~ c=d~ ~ set +o braceexpand + unset A echo ${A=a=}~ b=~ c=d~ ~ expected-stdout: a=/sweet b=/sweet c=d~ /sweet a=~ b=~ c=d~ /sweet --- +name: tilde-expand-2 +description: + Check tilde expansion works +env-setup: !HOME=/sweet! +stdin: + wd=$PWD + cd / + plus=$(print -r -- ~+) + minus=$(print -r -- ~-) + nix=$(print -r -- ~) + [[ $plus = / ]]; echo one $? . + [[ $minus = "$wd" ]]; echo two $? . + [[ $nix = /sweet ]]; echo nix $? . +expected-stdout: + one 0 . + two 0 . + nix 0 . +--- name: exit-err-1 description: Check some "exit on error" conditions stdin: + print '#!'"$__progname"'\nexec "$1"' >env + print '#!'"$__progname"'\nexit 1' >false + chmod +x env false + PATH=.:$PATH set -ex env false && echo something echo END @@ -5249,6 +5665,11 @@ name: exit-err-2 description: Check some "exit on error" edge conditions (POSIXly) stdin: + print '#!'"$__progname"'\nexec "$1"' >env + print '#!'"$__progname"'\nexit 1' >false + print '#!'"$__progname"'\nexit 0' >true + chmod +x env false + PATH=.:$PATH set -ex if env true; then env false && echo something @@ -5333,6 +5754,33 @@ expected-stdout: Stop in WD/zd/a (line 2 of Makefile). --- +name: exit-err-7 +description: + "set -e" regression (LP#1104543) +stdin: + set -e + bla() { + [ -x $PWD/nonexistant ] && $PWD/nonexistant + } + echo x + bla + echo y$? +expected-stdout: + x +expected-exit: 1 +--- +name: exit-err-8 +description: + "set -e" regression (Debian #700526) +stdin: + set -e + _db_cmd() { return $1; } + db_input() { _db_cmd 30; } + db_go() { _db_cmd 0; } + db_input || : + db_go + exit 0 +--- name: exit-enoent-1 description: SUSv4 says that the shell should exit with 126/127 in some situations @@ -5359,19 +5807,29 @@ name: exit-eval-1 description: Check eval vs substitution exit codes (ksh93 alike) stdin: + (exit 12) eval $(false) echo A $? + (exit 12) eval ' $(false)' echo B $? + (exit 12) eval " $(false)" echo C $? + (exit 12) eval "eval $(false)" echo D $? + (exit 12) eval 'eval '"$(false)" echo E $? IFS="$IFS:" + (exit 12) eval $(echo :; false) echo F $? + echo -n "G " + (exit 12) + eval 'echo $?' + echo H $? expected-stdout: A 0 B 1 @@ -5379,6 +5837,8 @@ expected-stdout: D 0 E 0 F 0 + G 12 + H 0 --- name: exit-trap-1 description: @@ -5390,6 +5850,140 @@ expected-stdout: hi expected-exit: 9 --- +name: exit-trap-2 +description: + Check that ERR and EXIT traps are run just like ksh93 does. + GNU bash does not run ERtrap in ±e eval-undef but runs it + twice (bug?) in +e eval-false, so does ksh93 (bug?), which + also has a bug to continue execution (echoing "and out" and + returning 0) in +e eval-undef. +file-setup: file 644 "x" + v=; unset v + trap 'echo EXtrap' EXIT + trap 'echo ERtrap' ERR + set $1 + echo "and run $2" + eval $2 + echo and out +file-setup: file 644 "xt" + v=; unset v + trap 'echo EXtrap' EXIT + trap 'echo ERtrap' ERR + set $1 + echo 'and run true' + true + echo and out +file-setup: file 644 "xf" + v=; unset v + trap 'echo EXtrap' EXIT + trap 'echo ERtrap' ERR + set $1 + echo 'and run false' + false + echo and out +file-setup: file 644 "xu" + v=; unset v + trap 'echo EXtrap' EXIT + trap 'echo ERtrap' ERR + set $1 + echo 'and run ${v?}' + ${v?} + echo and out +stdin: + runtest() { + rm -f rc + ( + "$__progname" "$@" + echo $? >rc + ) 2>&1 | sed \ + -e 's/parameter not set/parameter null or not set/' \ + -e 's/[[]6]//' -e 's/: eval: line 1//' -e 's/: line 6//' \ + -e "s^${__progname%.exe}\.*e*x*e*: \[[0-9]*]PROG" + } + xe=-e + echo : $xe + runtest x $xe true + echo = eval-true $(t1 @@ -5841,21 +6434,6 @@ expected-stdout: expected-stderr-pattern: /(Unrecognized character .... ignored at \..t4 line 1)*/ --- -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: Check that the utf8-mode flag is not set at non-interactive startup @@ -5892,7 +6470,7 @@ description: -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 +category: !os:hpux,!os:msys need-ctty: yes arguments: !-i! env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8! @@ -6116,6 +6694,17 @@ expected-stdout: source='PATH=$PATH:. command .' type='whence -v' --- +name: aliases-cmdline +description: + Check that aliases work from the command line (Debian #517009) + Note that due to the nature of the lexing process, defining + aliases in COMSUBs then immediately using them, and things + like 'alias foo=bar && foo', still fail. +stdin: + "$__progname" -c $'alias a="echo OK"\na' +expected-stdout: + OK +--- name: aliases-funcdef-1 description: Check if POSIX functions take precedences over aliases @@ -6176,7 +6765,6 @@ expected-stdout: name: arrays-2a description: Check if bash-style arrays work as expected -category: !smksh stdin: v="c d" foo=(a \$v "$v" '$v' b) @@ -6187,25 +6775,26 @@ expected-stdout: name: arrays-2b description: Check if bash-style arrays work as expected, with newlines -category: !smksh stdin: + print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "$x|"; done' >pfp + chmod +x pfp 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 + ./pfp "${#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 + ./pfp "${#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 + ./pfp "${#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| @@ -6228,7 +6817,6 @@ expected-stdout: name: arrays-4 description: Check if Korn Shell arrays with specified indices work as expected -category: !smksh stdin: v="c d" set -A foo -- [1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b @@ -6239,7 +6827,6 @@ expected-stdout: name: arrays-5 description: Check if bash-style arrays with specified indices work as expected -category: !smksh stdin: v="c d" foo=([1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b) @@ -6408,7 +6995,6 @@ expected-stdout: 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[*]} . @@ -6428,6 +7014,344 @@ expected-stdout: 1 barbaz . 2 16#a20 . --- +name: arrassign-basic +description: + Check basic whitespace conserving properties of wdarrassign +stdin: + a=($(echo a b)) + b=($(echo "a b")) + c=("$(echo "a b")") + d=("$(echo a b)") + a+=($(echo c d)) + b+=($(echo "c d")) + c+=("$(echo "c d")") + d+=("$(echo c d)") + echo ".a:${a[0]}.${a[1]}.${a[2]}.${a[3]}:" + echo ".b:${b[0]}.${b[1]}.${b[2]}.${b[3]}:" + echo ".c:${c[0]}.${c[1]}.${c[2]}.${c[3]}:" + echo ".d:${d[0]}.${d[1]}.${d[2]}.${d[3]}:" +expected-stdout: + .a:a.b.c.d: + .b:a.b.c.d: + .c:a b.c d..: + .d:a b.c d..: +--- +name: arrassign-fnc-none +description: + Check locality of array access inside a function +stdin: + function fn { + x+=(f) + echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + } + function rfn { + if [[ -n $BASH_VERSION ]]; then + y=() + else + set -A y + fi + y+=(f) + echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + } + x=(m m) + y=(m m) + echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" +expected-stdout: + .f0:m.m..: + .fn:m.m.f.: + .f1:m.m.f.: + .fn:m.m.f.f: + .f2:m.m.f.f: + .rf0:m.m..: + .rfn:f...: + .rf1:f...: + .rfn:f...: + .rf2:f...: +--- +name: arrassign-fnc-local +description: + Check locality of array access inside a function + with the bash/mksh/ksh93 local/typeset keyword + (note: ksh93 has no local; typeset works only in FKSH) +stdin: + function fn { + typeset x + x+=(f) + echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + } + function rfn { + if [[ -n $BASH_VERSION ]]; then + y=() + else + set -A y + fi + typeset y + y+=(f) + echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + } + function fnr { + typeset z + if [[ -n $BASH_VERSION ]]; then + z=() + else + set -A z + fi + z+=(f) + echo ".fnr:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + } + x=(m m) + y=(m m) + z=(m m) + echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + echo ".f0r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + fnr + echo ".f1r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + fnr + echo ".f2r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" +expected-stdout: + .f0:m.m..: + .fn:f...: + .f1:m.m..: + .fn:f...: + .f2:m.m..: + .rf0:m.m..: + .rfn:f...: + .rf1:...: + .rfn:f...: + .rf2:...: + .f0r:m.m..: + .fnr:f...: + .f1r:m.m..: + .fnr:f...: + .f2r:m.m..: +--- +name: arrassign-fnc-global +description: + Check locality of array access inside a function + with the mksh-specific global keyword +stdin: + function fn { + global x + x+=(f) + echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + } + function rfn { + set -A y + global y + y+=(f) + echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + } + function fnr { + global z + set -A z + z+=(f) + echo ".fnr:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + } + x=(m m) + y=(m m) + z=(m m) + echo ".f0:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f1:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + fn + echo ".f2:${x[0]}.${x[1]}.${x[2]}.${x[3]}:" + echo ".rf0:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf1:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + rfn + echo ".rf2:${y[0]}.${y[1]}.${y[2]}.${y[3]}:" + echo ".f0r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + fnr + echo ".f1r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" + fnr + echo ".f2r:${z[0]}.${z[1]}.${z[2]}.${z[3]}:" +expected-stdout: + .f0:m.m..: + .fn:m.m.f.: + .f1:m.m.f.: + .fn:m.m.f.f: + .f2:m.m.f.f: + .rf0:m.m..: + .rfn:f...: + .rf1:f...: + .rfn:f...: + .rf2:f...: + .f0r:m.m..: + .fnr:f...: + .f1r:f...: + .fnr:f...: + .f2r:f...: +--- +name: strassign-fnc-none +description: + Check locality of string access inside a function +stdin: + function fn { + x+=f + echo ".fn:$x:" + } + function rfn { + y= + y+=f + echo ".rfn:$y:" + } + x=m + y=m + echo ".f0:$x:" + fn + echo ".f1:$x:" + fn + echo ".f2:$x:" + echo ".rf0:$y:" + rfn + echo ".rf1:$y:" + rfn + echo ".rf2:$y:" +expected-stdout: + .f0:m: + .fn:mf: + .f1:mf: + .fn:mff: + .f2:mff: + .rf0:m: + .rfn:f: + .rf1:f: + .rfn:f: + .rf2:f: +--- +name: strassign-fnc-local +description: + Check locality of string access inside a function + with the bash/mksh/ksh93 local/typeset keyword + (note: ksh93 has no local; typeset works only in FKSH) +stdin: + function fn { + typeset x + x+=f + echo ".fn:$x:" + } + function rfn { + y= + typeset y + y+=f + echo ".rfn:$y:" + } + function fnr { + typeset z + z= + z+=f + echo ".fnr:$z:" + } + x=m + y=m + z=m + echo ".f0:$x:" + fn + echo ".f1:$x:" + fn + echo ".f2:$x:" + echo ".rf0:$y:" + rfn + echo ".rf1:$y:" + rfn + echo ".rf2:$y:" + echo ".f0r:$z:" + fnr + echo ".f1r:$z:" + fnr + echo ".f2r:$z:" +expected-stdout: + .f0:m: + .fn:f: + .f1:m: + .fn:f: + .f2:m: + .rf0:m: + .rfn:f: + .rf1:: + .rfn:f: + .rf2:: + .f0r:m: + .fnr:f: + .f1r:m: + .fnr:f: + .f2r:m: +--- +name: strassign-fnc-global +description: + Check locality of string access inside a function + with the mksh-specific global keyword +stdin: + function fn { + global x + x+=f + echo ".fn:$x:" + } + function rfn { + y= + global y + y+=f + echo ".rfn:$y:" + } + function fnr { + global z + z= + z+=f + echo ".fnr:$z:" + } + x=m + y=m + z=m + echo ".f0:$x:" + fn + echo ".f1:$x:" + fn + echo ".f2:$x:" + echo ".rf0:$y:" + rfn + echo ".rf1:$y:" + rfn + echo ".rf2:$y:" + echo ".f0r:$z:" + fnr + echo ".f1r:$z:" + fnr + echo ".f2r:$z:" +expected-stdout: + .f0:m: + .fn:mf: + .f1:mf: + .fn:mff: + .f2:mff: + .rf0:m: + .rfn:f: + .rf1:f: + .rfn:f: + .rf2:f: + .f0r:m: + .fnr:f: + .f1r:f: + .fnr:f: + .f2r:f: +--- name: varexpand-substr-1 description: Check if bash-style substring expansion works @@ -6557,9 +7481,36 @@ stdin: typeset -i8 foo=10 bar=baz unset baz + bla=foo print ${foo@#} ${bar@#} ${baz@#} . + print ${foo@#123} ${bar@#456} ${baz@#789} . + print ${foo@#bla} ${bar@#bar} ${baz@#OPTIND} . expected-stdout: - D50219A0 20E5DB5B 00000001 . + D50219A0 20E5DB5B 00000000 . + 554A1C76 004A212E CB209562 . + 6B21CF91 20E5DB5B 124EA49D . +--- +name: varexpand-special-quote +description: + Check special ${var@Q} expansion for quoted strings +stdin: + set +U + i=x + j=a\ b + k=$'c + d\xA0''e€f' + print -r -- "" + s="u=${i@Q} v=${j@Q} w=${k@Q}" + print -r -- "s=\"$s\"" + eval "$s" + typeset -p u v w +expected-stdout: + + s="u=x v='a b' w=$'c\nd\240e\u20ACf'" + typeset u=x + typeset v='a b' + typeset w=$'c\nd\240e\u20ACf' --- name: varexpand-null-1 description: @@ -6577,7 +7528,9 @@ name: varexpand-null-2 description: Ensure empty strings, when quoted, are expanded as empty strings stdin: - printf '<%s> ' 1 "${a}" 2 "${a#?}" + "${b%?}" 3 "${a=}" + "${b/c/d}" + print '#!'"$__progname"'\nfor x in "$@"; do print -nr -- "<$x> "; done' >pfs + chmod +x pfs + ./pfs 1 "${a}" 2 "${a#?}" + "${b%?}" 3 "${a=}" + "${b/c/d}" echo . expected-stdout: <1> <> <2> <> <+> <> <3> <> <+> <> . @@ -6600,16 +7553,13 @@ expected-stdout: --- name: print-nul-chars description: - Check handling of NUL characters for print and read - note: second line should output “4 3” but we cannot - handle NUL characters in strings yet + Check handling of NUL characters for print and COMSUB stdin: - print $(($(print '<\0>' | wc -c))) x=$(print '<\0>') - print $(($(print "$x" | wc -c))) ${#x} -expected-stdout: - 4 - 3 2 + print $(($(print '<\0>' | wc -c))) $(($(print "$x" | wc -c))) \ + ${#x} "$x" '<\0>' +expected-stdout-pattern: + /^4 3 2 <> <\0>$/ --- name: print-escapes description: @@ -6686,7 +7636,9 @@ name: dollar-quoted-strings description: Check backslash expansion by $'…' strings stdin: - printf '%s\n' $'\ \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/ \1\2\3\4\5\6' \ + print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn + chmod +x pfn + ./pfn $'\ \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/ \1\2\3\4\5\6' \ $'a\0b' $'a\01b' $'\7\8\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I' \ $'\J\K\L\M\N\O\P\Q\R\S\T\U1\V\W\X\Y\Z\[\\\]\^\_\`\a\b\d\e' \ $'\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u1\v\w\x1\y\z\{\|\}\~ $x' \ @@ -6735,34 +7687,55 @@ expected-stdout: 00000070 EF BF BD 0A C4 A3 0A 66 - 6E 0A 13 34 0A 9C 0A 9C |.......fn..4....| 00000080 35 0A 01 0A 01 0A 7F 0A - 02 82 AC 0A 61 0A 62 0A |5...........a.b.| --- -name: dollar-quotes-in-heredocs +name: dollar-quotes-in-heredocs-strings description: - They are, however, not parsed in here documents + They are, however, not parsed in here documents, here strings + (outside of string delimiters) or regular strings, but in + parameter substitutions. stdin: cat <&- || echo rab + foo 2>/dev/null || echo rab expected-stdout: baz bar @@ -6813,11 +7786,13 @@ stdin: mk 'foo ()' >f-dash mk 'function foo ()' >f-bash mk 'function stop ()' stop >f-stop + print '#!'"$__progname"'\nprint -r -- "${0%/f-argh}"' >f-argh chmod +x f-* - echo "korn: $(./f-korn)" - echo "dash: $(./f-dash)" - echo "bash: $(./f-bash)" - echo "stop: $(./f-stop)" + u=$(./f-argh) + x="korn: $(./f-korn)"; echo "${x/@("$u")/.}" + x="dash: $(./f-dash)"; echo "${x/@("$u")/.}" + x="bash: $(./f-bash)"; echo "${x/@("$u")/.}" + x="stop: $(./f-stop)"; echo "${x/@("$u")/.}" expected-stdout: korn: bar='foo' dash: bar='./f-dash' @@ -7343,6 +8318,7 @@ expected-stdout: name: ulimit-1 description: Check if we can use a specific syntax idiom for ulimit +category: !os:syllable stdin: if ! x=$(ulimit -d) || [[ $x = unknown ]]; then #echo expected to fail on this OS @@ -7353,6 +8329,36 @@ stdin: expected-stdout: okay --- +name: redir-1 +description: + Check some of the most basic invariants of I/O redirection +stdin: + i=0 + function d { + print o$i. + print -u2 e$((i++)). + } + d >a 2>b + echo =1= + cat a + echo =2= + cat b + echo =3= + d 2>&1 >c + echo =4= + cat c + echo =5= +expected-stdout: + =1= + o0. + =2= + e0. + =3= + e1. + =4= + o1. + =5= +--- name: bashiop-1 description: Check if GNU bash-like I/O redirection works @@ -7561,6 +8567,9 @@ description: global environment. Inspired by PR 2450. stdin: + print '#!'"$__progname"'\nunset RANDOM\nexport | while IFS= read -r' \ + 'RANDOM; do eval '\''print -r -- "$RANDOM=$'\''"$RANDOM"'\'\"\'\; \ + done >env; chmod +x env; PATH=.:$PATH function k { if [ x$FOO != xbar ]; then echo 1 @@ -7621,6 +8630,7 @@ 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 +category: shell:legacy-no file-setup: file 644 "test.sh" echo >&3 Fowl stdin: @@ -7643,6 +8653,18 @@ stdin: expected-stdout: Fowl --- +name: fd-cloexec-3 +description: + Verify that file descriptors > 2 are not private for LEGACY KSH +category: shell:legacy-yes +file-setup: file 644 "test.sh" + echo >&3 Fowl +stdin: + exec 3>&1 + "$__progname" test.sh +expected-stdout: + Fowl +--- name: comsub-1a description: COMSUB are now parsed recursively, so this works @@ -7650,46 +8672,39 @@ description: 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)} + echo 1 $(case 1 in (1) echo yes;; (2) echo no;; esac) . + echo 2 $(case 1 in 1) echo yes;; 2) echo no;; esac) . + TEST=1234; echo 3 ${TEST: $(case 1 in (1) echo 1;; (*) echo 2;; esac)} . + TEST=5678; echo 4 ${TEST: $(case 1 in 1) echo 1;; *) echo 2;; esac)} . + a=($(case 1 in (1) echo 1;; (*) echo 2;; esac)); echo 5 ${a[0]} . + a=($(case 1 in 1) echo 1;; *) echo 2;; esac)); echo 6 ${a[0]} . expected-stdout: - yes - yes - 234 - 678 + 1 yes . + 2 yes . + 3 234 . + 4 678 . + 5 1 . + 6 1 . --- name: comsub-1b description: COMSUB are now parsed recursively, so this works - Fails on GNU bash even, ksh93 passes + Fails on: pdksh bash2 bash3 bash4 zsh + Passes on: ksh93 mksh(20110313+) 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. + echo 1 $(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10)) . + echo 2 $(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20)) . + (( a = $(case 1 in (1) echo 1;; (*) echo 2;; esac) )); echo 3 $a . + (( a = $(case 1 in 1) echo 1;; *) echo 2;; esac) )); echo 4 $a . + a=($(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))); echo 5 ${a[0]} . + a=($(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))); echo 6 ${a[0]} . 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. + 1 11 . + 2 21 . + 3 1 . + 4 1 . + 5 11 . + 6 21 . --- name: comsub-2 description: @@ -7714,6 +8729,7 @@ description: is a must (a non-recursive parser cannot pass all three of these test cases, especially the ‘#’ is difficult) stdin: + print '#!'"$__progname"'\necho 1234' >id; chmod +x id; PATH=.:$PATH echo $(typeset -i10 x=16#20; echo $x) echo $(typeset -Uui16 x=16#$(id -u) ) . @@ -7731,20 +8747,38 @@ description: Check the tree dump functions for !MKSH_SMALL functionality category: !smksh stdin: - x() { case $1 in a) a+=b ;;& *) c+=(d e) ;; esac; } + x() { case $1 in u) echo x ;;& *) echo $1 ;; esac; } typeset -f x expected-stdout: x() { case $1 in - (a) - a+=b + (u) + echo x ;| (*) - set -A c+ -- d e + echo $1 ;; esac } --- +name: comsub-5 +description: + Check COMSUB works with aliases (does not expand them twice) +stdin: + print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn + chmod +x pfn + alias echo='echo a' + foo() { + ./pfn "$(echo foo)" + } + ./pfn "$(echo b)" + typeset -f foo +expected-stdout: + a b + foo() { + ./pfn "$(echo foo )" + } +--- name: comsub-torture description: Check the tree dump functions work correctly @@ -7786,7 +8820,7 @@ stdin: #TSELECT select file in *; do echo "<$file>" ; break ; done #TFOR_TTIME - for i in {1,2,3} ; do time echo $i ; done + time for i in {1,2,3} ; do 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 @@ -7843,6 +8877,10 @@ stdin: install -c -o root -g wheel -m 664 /dev/null /etc/motd print -- "$x\n" >/etc/motd fi + #wdarrassign + case x in + x) a+=b; c+=(d e) + esac #0 EOD expected-stdout: @@ -7924,25 +8962,25 @@ expected-stdout: 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 + time for i in {1,2,3} ; do echo $i ; done } inline_TFOR_TTIME() { - for i in {1,2,3} + time for i in {1,2,3} do - time echo $i + echo $i done } function comsub_TFOR_TTIME { x=$( - for i in {1,2,3} ; do time echo $i ; done + time for i in {1,2,3} ; do echo $i ; done ); } function comsub_TFOR_TTIME { - x=$(for i in {1,2,3} ; do time echo $i ; done ) + x=$(time for i in {1,2,3} ; do echo $i ; done ) } function reread_TFOR_TTIME { x=$(( - for i in {1,2,3} ; do time echo $i ; done + time for i in {1,2,3} ; do 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 ) + x=$(( time for i in {1,2,3} ; do 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 @@ -8113,7 +9151,7 @@ expected-stdout: EOFN } inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() { - cat >|bar <<"EOFN" + cat >|bar <<"EOFN" foo EOFN @@ -8124,7 +9162,7 @@ expected-stdout: EOFN ); } function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP { - x=$(cat >|bar <<"EOFN" + x=$(cat >|bar <<"EOFN" foo EOFN ) @@ -8135,7 +9173,7 @@ expected-stdout: EOFN )|tr u x); } function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP { - x=$(( cat >|bar <<"EOFN" + x=$(( cat >|bar <<"EOFN" foo EOFN ) | tr u x ) @@ -8146,7 +9184,7 @@ expected-stdout: EOFI } inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() { - cat >bar <<-EOFI + cat >bar <<-EOFI foo EOFI @@ -8157,7 +9195,7 @@ expected-stdout: EOFI ); } function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP { - x=$(cat >bar <<-EOFI + x=$(cat >bar <<-EOFI foo EOFI ) @@ -8168,7 +9206,7 @@ expected-stdout: EOFI )|tr u x); } function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP { - x=$(( cat >bar <<-EOFI + x=$(( cat >bar <<-EOFI foo EOFI ) | tr u x ) @@ -8259,7 +9297,7 @@ expected-stdout: EOFN); echo $x } inline_heredoc_closed() { - x=$(cat <&1 <<-EOF + [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 1,/^\$/d 0a $x @@ -8371,7 +9409,7 @@ expected-stdout: 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 + 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 @@ -8398,7 +9436,7 @@ expected-stdout: 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 + 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 @@ -8408,6 +9446,390 @@ expected-stdout: 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 ) } + inline_wdarrassign() { + case x in + x) a+=b; c+=(d e) + esac + } + inline_wdarrassign() { + case x in + (x) + a+=b + set -A c+ -- d e + ;; + esac + } + function comsub_wdarrassign { x=$( + case x in + x) a+=b; c+=(d e) + esac + ); } + function comsub_wdarrassign { + x=$(case x in (x) a+=b ; set -A c+ -- d e ;; esac ) + } + function reread_wdarrassign { x=$(( + case x in + x) a+=b; c+=(d e) + esac + )|tr u x); } + function reread_wdarrassign { + x=$(( case x in (x) a+=b ; set -A c+ -- d e ;; esac ) | tr u x ) + } +--- +name: comsub-torture-io +description: + Check the tree dump functions work correctly with I/O redirection +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" >&3 + #TPAREN_TPIPE_TLIST + (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3 + #TAND_TOR + cmd >&3 && >&3 echo ja || echo >&3 nein + #TSELECT + select file in *; do echo "<$file>" ; break >&3 ; done >&3 + #TFOR_TTIME + for i in {1,2,3} ; do time >&3 echo $i ; done >&3 + #TCASE + case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3 + #TIF_TBANG_TDBRACKET_TELIF + if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3 + #TWHILE + i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3 + #TUNTIL + i=10; until (( !--i )) >&3 ; do echo $i; done >&3 + #TCOPROC + cat * >&3 |& >&3 ls + #TFUNCT_TBRACE_TASYNC + function korn { echo eins; echo >&3 zwei ; } + bourne () { logger * >&3 & } + #COMSUB_EXPRSUB + echo $(true >&3) $((1+ 2)) + #0 + EOD +expected-stdout: + inline_TCOM() { + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3 + } + inline_TCOM() { + vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" >&3 + } + function comsub_TCOM { x=$( + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3 + ); } + function comsub_TCOM { + x=$(vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" >&3 ) + } + function reread_TCOM { x=$(( + vara=1 varb='2 3' cmd arg1 $arg2 "$arg3 4" >&3 + )|tr u x); } + function reread_TCOM { + x=$(( vara=1 varb="2 3" cmd arg1 $arg2 "$arg3 4" >&3 ) | tr u x ) + } + inline_TPAREN_TPIPE_TLIST() { + (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3 + } + inline_TPAREN_TPIPE_TLIST() { + ( echo $foo | tr -dc 0-9 >&3 + echo >&3 ) >&3 + } + function comsub_TPAREN_TPIPE_TLIST { x=$( + (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3 + ); } + function comsub_TPAREN_TPIPE_TLIST { + x=$(( echo $foo | tr -dc 0-9 >&3 ; echo >&3 ) >&3 ) + } + function reread_TPAREN_TPIPE_TLIST { x=$(( + (echo $foo | tr -dc 0-9 >&3; echo >&3) >&3 + )|tr u x); } + function reread_TPAREN_TPIPE_TLIST { + x=$(( ( echo $foo | tr -dc 0-9 >&3 ; echo >&3 ) >&3 ) | tr u x ) + } + inline_TAND_TOR() { + cmd >&3 && >&3 echo ja || echo >&3 nein + } + inline_TAND_TOR() { + cmd >&3 && echo ja >&3 || echo nein >&3 + } + function comsub_TAND_TOR { x=$( + cmd >&3 && >&3 echo ja || echo >&3 nein + ); } + function comsub_TAND_TOR { + x=$(cmd >&3 && echo ja >&3 || echo nein >&3 ) + } + function reread_TAND_TOR { x=$(( + cmd >&3 && >&3 echo ja || echo >&3 nein + )|tr u x); } + function reread_TAND_TOR { + x=$(( cmd >&3 && echo ja >&3 || echo nein >&3 ) | tr u x ) + } + inline_TSELECT() { + select file in *; do echo "<$file>" ; break >&3 ; done >&3 + } + inline_TSELECT() { + select file in * + do + echo "<$file>" + break >&3 + done >&3 + } + function comsub_TSELECT { x=$( + select file in *; do echo "<$file>" ; break >&3 ; done >&3 + ); } + function comsub_TSELECT { + x=$(select file in * ; do echo "<$file>" ; break >&3 ; done >&3 ) + } + function reread_TSELECT { x=$(( + select file in *; do echo "<$file>" ; break >&3 ; done >&3 + )|tr u x); } + function reread_TSELECT { + x=$(( select file in * ; do echo "<$file>" ; break >&3 ; done >&3 ) | tr u x ) + } + inline_TFOR_TTIME() { + for i in {1,2,3} ; do time >&3 echo $i ; done >&3 + } + inline_TFOR_TTIME() { + for i in {1,2,3} + do + time echo $i >&3 + done >&3 + } + function comsub_TFOR_TTIME { x=$( + for i in {1,2,3} ; do time >&3 echo $i ; done >&3 + ); } + function comsub_TFOR_TTIME { + x=$(for i in {1,2,3} ; do time echo $i >&3 ; done >&3 ) + } + function reread_TFOR_TTIME { x=$(( + for i in {1,2,3} ; do time >&3 echo $i ; done >&3 + )|tr u x); } + function reread_TFOR_TTIME { + x=$(( for i in {1,2,3} ; do time echo $i >&3 ; done >&3 ) | tr u x ) + } + inline_TCASE() { + case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3 + } + inline_TCASE() { + case $foo in + (1) + echo eins >&3 + ;& + (2) + echo zwei >&3 + ;| + (*) + echo kann net bis drei zählen >&3 + ;; + esac >&3 + } + function comsub_TCASE { x=$( + case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3 + ); } + function comsub_TCASE { + x=$(case $foo in (1) echo eins >&3 ;& (2) echo zwei >&3 ;| (*) echo kann net bis drei zählen >&3 ;; esac >&3 ) + } + function reread_TCASE { x=$(( + case $foo in 1) echo eins >&3;& 2) echo zwei >&3 ;| *) echo kann net bis drei zählen >&3;; esac >&3 + )|tr u x); } + function reread_TCASE { + x=$(( case $foo in (1) echo eins >&3 ;& (2) echo zwei >&3 ;| (*) echo kann net bis drei zählen >&3 ;; esac >&3 ) | tr u x ) + } + inline_TIF_TBANG_TDBRACKET_TELIF() { + if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3 + } + inline_TIF_TBANG_TDBRACKET_TELIF() { + if ! [[ 1 = 1 ]] >&3 + then + echo eins + elif [[ 1 = 2 ]] >&3 + then + echo zwei + else + echo drei + fi >&3 + } + function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$( + if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3 + ); } + function comsub_TIF_TBANG_TDBRACKET_TELIF { + x=$(if ! [[ 1 = 1 ]] >&3 ; then echo eins ; elif [[ 1 = 2 ]] >&3 ; then echo zwei ; else echo drei ; fi >&3 ) + } + function reread_TIF_TBANG_TDBRACKET_TELIF { x=$(( + if ! [[ 1 = 1 ]] >&3 ; then echo eins; elif [[ 1 = 2 ]] >&3; then echo zwei ;else echo drei; fi >&3 + )|tr u x); } + function reread_TIF_TBANG_TDBRACKET_TELIF { + x=$(( if ! [[ 1 = 1 ]] >&3 ; then echo eins ; elif [[ 1 = 2 ]] >&3 ; then echo zwei ; else echo drei ; fi >&3 ) | tr u x ) + } + inline_TWHILE() { + i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3 + } + inline_TWHILE() { + i=1 + while let " i < 10 " >&3 + do + echo $i + let ++i + done >&3 + } + function comsub_TWHILE { x=$( + i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3 + ); } + function comsub_TWHILE { + x=$(i=1 ; while let " i < 10 " >&3 ; do echo $i ; let ++i ; done >&3 ) + } + function reread_TWHILE { x=$(( + i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3 + )|tr u x); } + function reread_TWHILE { + x=$(( i=1 ; while let " i < 10 " >&3 ; do echo $i ; let ++i ; done >&3 ) | tr u x ) + } + inline_TUNTIL() { + i=10; until (( !--i )) >&3 ; do echo $i; done >&3 + } + inline_TUNTIL() { + i=10 + until let " !--i " >&3 + do + echo $i + done >&3 + } + function comsub_TUNTIL { x=$( + i=10; until (( !--i )) >&3 ; do echo $i; done >&3 + ); } + function comsub_TUNTIL { + x=$(i=10 ; until let " !--i " >&3 ; do echo $i ; done >&3 ) + } + function reread_TUNTIL { x=$(( + i=10; until (( !--i )) >&3 ; do echo $i; done >&3 + )|tr u x); } + function reread_TUNTIL { + x=$(( i=10 ; until let " !--i " >&3 ; do echo $i ; done >&3 ) | tr u x ) + } + inline_TCOPROC() { + cat * >&3 |& >&3 ls + } + inline_TCOPROC() { + cat * >&3 |& + ls >&3 + } + function comsub_TCOPROC { x=$( + cat * >&3 |& >&3 ls + ); } + function comsub_TCOPROC { + x=$(cat * >&3 |& ls >&3 ) + } + function reread_TCOPROC { x=$(( + cat * >&3 |& >&3 ls + )|tr u x); } + function reread_TCOPROC { + x=$(( cat * >&3 |& ls >&3 ) | tr u x ) + } + inline_TFUNCT_TBRACE_TASYNC() { + function korn { echo eins; echo >&3 zwei ; } + bourne () { logger * >&3 & } + } + inline_TFUNCT_TBRACE_TASYNC() { + function korn { + echo eins + echo zwei >&3 + } + bourne() { + logger * >&3 & + } + } + function comsub_TFUNCT_TBRACE_TASYNC { x=$( + function korn { echo eins; echo >&3 zwei ; } + bourne () { logger * >&3 & } + ); } + function comsub_TFUNCT_TBRACE_TASYNC { + x=$(function korn { echo eins ; echo zwei >&3 ; } ; bourne() { logger * >&3 & } ) + } + function reread_TFUNCT_TBRACE_TASYNC { x=$(( + function korn { echo eins; echo >&3 zwei ; } + bourne () { logger * >&3 & } + )|tr u x); } + function reread_TFUNCT_TBRACE_TASYNC { + x=$(( function korn { echo eins ; echo zwei >&3 ; } ; bourne() { logger * >&3 & } ) | tr u x ) + } + inline_COMSUB_EXPRSUB() { + echo $(true >&3) $((1+ 2)) + } + inline_COMSUB_EXPRSUB() { + echo $(true >&3 ) $((1+ 2)) + } + function comsub_COMSUB_EXPRSUB { x=$( + echo $(true >&3) $((1+ 2)) + ); } + function comsub_COMSUB_EXPRSUB { + x=$(echo $(true >&3 ) $((1+ 2)) ) + } + function reread_COMSUB_EXPRSUB { x=$(( + echo $(true >&3) $((1+ 2)) + )|tr u x); } + function reread_COMSUB_EXPRSUB { + x=$(( echo $(true >&3 ) $((1+ 2)) ) | tr u x ) + } +--- +name: funsub-1 +description: + Check that non-subenvironment command substitution works +stdin: + set -e + foo=bar + echo "ob $foo ." + echo "${ + echo "ib $foo :" + foo=baz + echo "ia $foo :" + false + }" . + echo "oa $foo ." +expected-stdout: + ob bar . + ib bar : + ia baz : . + oa baz . +--- +name: funsub-2 +description: + You can now reliably use local and return in funsubs + (not exit though) +stdin: + x=q; e=1; x=${ echo a; e=2; echo x$e;}; echo 1:y$x,$e,$?. + x=q; e=1; x=${ echo a; typeset e=2; echo x$e;}; echo 2:y$x,$e,$?. + x=q; e=1; x=${ echo a; typeset e=2; return 3; echo x$e;}; echo 3:y$x,$e,$?. +expected-stdout: + 1:ya x2,2,0. + 2:ya x2,1,0. + 3:ya,1,3. --- name: test-stnze-1 description: @@ -8499,103 +9921,9 @@ expected-stdout: 11 0 12 0 --- -name: event-subst-1a -description: - Check that '!' substitution in interactive mode works -category: !smksh -file-setup: file 755 "falsetto" - #! /bin/sh - echo molto bene - exit 42 -file-setup: file 755 "!false" - #! /bin/sh - echo si -need-ctty: yes -arguments: !-i! -stdin: - export PATH=.:$PATH - falsetto - echo yeap - !false -expected-exit: 42 -expected-stdout: - molto bene - yeap - molto bene -expected-stderr-pattern: - /.*/ ---- -name: event-subst-1b -description: - Check that '!' substitution in interactive mode works - even when a space separates it from the search command, - which is not what GNU bash provides but required for the - other regression tests below to check -category: !smksh -file-setup: file 755 "falsetto" - #! /bin/sh - echo molto bene - exit 42 -file-setup: file 755 "!" - #! /bin/sh - echo si -need-ctty: yes -arguments: !-i! -stdin: - export PATH=.:$PATH - falsetto - echo yeap - ! false -expected-exit: 42 -expected-stdout: - molto bene - yeap - molto bene -expected-stderr-pattern: - /.*/ ---- -name: event-subst-2 -description: - Check that '!' substitution in interactive mode - does not break things -category: !smksh -file-setup: file 755 "falsetto" - #! /bin/sh - echo molto bene - exit 42 -file-setup: file 755 "!" - #! /bin/sh - echo si -need-ctty: yes -arguments: !-i! -env-setup: !ENV=./Env! -file-setup: file 644 "Env" - PS1=X -stdin: - export PATH=.:$PATH - falsetto - echo yeap - !false - echo meow - ! false - echo = $? - if - ! false; then echo foo; else echo bar; fi -expected-stdout: - molto bene - yeap - molto bene - meow - molto bene - = 42 - foo -expected-stderr-pattern: - /.*/ ---- name: event-subst-3 description: Check that '!' substitution in noninteractive mode is ignored -category: !smksh file-setup: file 755 "falsetto" #! /bin/sh echo molto bene @@ -8621,6 +9949,38 @@ expected-stdout: = 0 foo --- +name: event-subst-0 +description: + Check that '!' substitution in interactive mode is ignored +need-ctty: yes +arguments: !-i! +file-setup: file 755 "falsetto" + #! /bin/sh + echo molto bene + exit 42 +file-setup: file 755 "!false" + #! /bin/sh + echo si +stdin: + export PATH=.:$PATH + falsetto + echo yeap + !false + echo meow + ! false + echo = $? + if + ! false; then echo foo; else echo bar; fi +expected-stdout: + molto bene + yeap + si + meow + = 0 + foo +expected-stderr-pattern: + /.*/ +--- name: nounset-1 description: Check that "set -u" matches (future) SUSv4 requirement @@ -8940,12 +10300,38 @@ description: mandates it shall be treated as string but escapes shall be expanded. stdin: - test -n "$POSH_VERSION" || set -o sh + test -n "$POSH_VERSION" || set -o posix echo -n 'foo\x40bar' echo -e '\tbaz' expected-stdout: foo\x40bar-e \tbaz --- +name: echo-test-3-mnbsd +description: + Test what the echo builtin does, and test a compatibility flag. +category: mnbsdash +stdin: + "$__progname" -c 'echo -n 1=\\x40$1; echo -e \\x2E' -- foo bar + "$__progname" -o posix -c 'echo -n 2=\\x40$1; echo -e \\x2E' -- foo bar + "$__progname" -o sh -c 'echo -n 3=\\x40$1; echo -e \\x2E' -- foo bar +expected-stdout: + 1=@foo. + 2=\x40foo-e \x2E + 3=\x40bar. +--- +name: echo-test-3-normal +description: + Test what the echo builtin does, and test a compatibility flag. +category: !mnbsdash +stdin: + "$__progname" -c 'echo -n 1=\\x40$1; echo -e \\x2E' -- foo bar + "$__progname" -o posix -c 'echo -n 2=\\x40$1; echo -e \\x2E' -- foo bar + "$__progname" -o sh -c 'echo -n 3=\\x40$1; echo -e \\x2E' -- foo bar +expected-stdout: + 1=@foo. + 2=\x40foo-e \x2E + 3=\x40foo-e \x2E +--- name: utilities-getopts-1 description: getopts sets OPTIND correctly for unparsed option @@ -9242,3 +10628,118 @@ expected-stdout: b x --- +name: case-braces +description: + Check that case end tokens are not mixed up (Debian #220272) +stdin: + i=0 + for value in 'x' '}' 'esac'; do + print -n "$((++i))($value)bourne " + case $value in + }) echo brace ;; + *) echo no ;; + esac + print -n "$((++i))($value)korn " + case $value { + esac) echo esac ;; + *) echo no ;; + } + done +expected-stdout: + 1(x)bourne no + 2(x)korn no + 3(})bourne brace + 4(})korn no + 5(esac)bourne no + 6(esac)korn esac +--- +name: command-shift +description: + Check that 'command shift' works +stdin: + function snc { + echo "before 0='$0' 1='$1' 2='$2'" + shift + echo "after 0='$0' 1='$1' 2='$2'" + } + function swc { + echo "before 0='$0' 1='$1' 2='$2'" + command shift + echo "after 0='$0' 1='$1' 2='$2'" + } + echo = without command + snc 一 二 + echo = with command + swc 一 二 + echo = done +expected-stdout: + = without command + before 0='snc' 1='一' 2='二' + after 0='snc' 1='二' 2='' + = with command + before 0='swc' 1='一' 2='二' + after 0='swc' 1='二' 2='' + = done +--- +name: duffs-device +description: + Check that the compiler did not optimise-break them + (lex.c has got a similar one in SHEREDELIM) +stdin: + set +U + s= + typeset -i1 i=0 + while (( ++i < 256 )); do + s+=${i#1#} + done + s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.' + typeset -p s +expected-stdout: + typeset s=$'\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\E\034\035\036\037 !"#$%&\047()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\u00A0\u20AC\uFFFD\357\277\276\357\277\277\360\220\200\200.' +--- +name: stateptr-underflow +description: + This check overflows an Xrestpos stored in a short in R40 +category: fastbox +stdin: + 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 + } + + i=-1 + s= + while (( ++i < 12120 )); do + s+=a + done + Lb64decode $s >/dev/null +--- diff --git a/src/dot.mkshrc b/src/dot.mkshrc index 518f031..5ea4b91 100644 --- a/src/dot.mkshrc +++ b/src/dot.mkshrc @@ -1,7 +1,8 @@ # $Id$ -# $MirOS: src/bin/mksh/dot.mkshrc,v 1.65 2011/08/27 18:06:40 tg Exp $ +# $MirOS: src/bin/mksh/dot.mkshrc,v 1.77 2013/02/17 15:58:26 tg Exp $ #- -# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 +# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013 # Thorsten Glaser # # Provided that these terms and disclaimer and all copyright notices @@ -21,19 +22,21 @@ #- # ${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 +PS1='#'; (( USER_ID )) && PS1='$'; [[ ${HOSTNAME:=$(ulimit -c 0; hostname -s \ + 2>/dev/null)} = *([ ]|localhost) ]] && HOSTNAME=$(ulimit -c 0; hostname \ + 2>/dev/null); : ${EDITOR:=/bin/ed} ${HOSTNAME:=nil} ${TERM:=vt100} function precmd { local e=$? (( e )) && print -n "$e|" + # precmd is required to retain the errorlevel when ${ …;} is used + return $e } -PS1=$'\001\r''$(precmd)${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \? - )}@${HOSTNAME%%.*}:$(local d=${PWD:-?} p=~; [[ $p = ?(*/) ]] || \ +PS1=$'\001\r''${ precmd;}${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \? + )}@${HOSTNAME%%.*}:${ local e=$? 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 " + p=; print -nr -- "$p$d"; return $e;} '"$PS1 " : ${MKSH:=$(whence -p mksh)}; export EDITOR HOSTNAME MKSH TERM USER alias ls=ls unalias ls @@ -41,20 +44,20 @@ alias l='ls -F' alias la='l -a' alias ll='l -l' alias lo='l -alo' -whence -p rot13 >&- || alias rot13='tr \ +whence -p rot13 >/dev/null || alias rot13='tr \ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \ nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' -whence -p hd >&- || function hd { +whence -p hd >/dev/null || 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") +DIRSTACKBASE=$(realpath ~/. 2>/dev/null || print -nr -- "${HOME:-/}") set -A DIRSTACK function chpwd { - DIRSTACK[0]=$(realpath . 2>&- || print -r -- "$PWD") + DIRSTACK[0]=$(realpath . 2>/dev/null || print -r -- "$PWD") [[ $DIRSTACKBASE = ?(*/) ]] || \ DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/~} : @@ -75,10 +78,8 @@ function cd_csh { } function dirs { local d dwidth - local -i isnoglob=0 fl=0 fv=0 fn=0 cpos=0 + local -i 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 ;; @@ -117,18 +118,15 @@ function dirs { done print fi - (( isnoglob )) || set +o noglob return 0 } function popd { local d fa - local -i isnoglob=0 n=1 + local -i n=1 - [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1 - set -o noglob while getopts ":0123456789lvn" d; do case $d { - (l|v|n) fa="$fa -$d" ;; + (l|v|n) fa+=" -$d" ;; (+*) n=2 break ;; (*) print -u2 'Usage: popd [-lvn] [+].' @@ -156,18 +154,15 @@ function popd { 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 + local -i n=1 - [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1 - set -o noglob while getopts ":0123456789lvn" d; do case $d { - (l|v|n) fa="$fa -$d" ;; + (l|v|n) fa+=" -$d" ;; (+*) n=2 break ;; (*) print -u2 'Usage: pushd [-lvn] [|+].' @@ -201,7 +196,6 @@ function pushd { set -A DIRSTACK -- placeholder "${DIRSTACK[@]}" cd_csh "$1" || return 1 fi - (( isnoglob )) || set +o noglob dirs $fa } @@ -229,7 +223,7 @@ function Lb64decode { 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 -i i=0 j=0 n=${#s} p=0 v x local -i16 o while (( i < n )); do @@ -252,7 +246,10 @@ function Lb64decode { p=0 ;; } - t=$t\\x${o#16#} + t+=\\x${o#16#} + (( ++j & 4095 )) && continue + print -n $t + t= done print -n $t (( u )) || set -U @@ -278,14 +275,14 @@ function Lb64encode { (( v |= j << 8 )) (( j = i < n ? s[i++] : 0 )) (( v |= j )) - t=$t${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]} + 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]} + t+=$c${Lb64encode_code[v & 63]} elif (( i == n + 1 )); then - t=$t$c= + t+=$c= else - t=$t== + t+=== fi if (( ${#t} == 76 || i >= n )); then print $t @@ -332,6 +329,9 @@ function Lnzaathash { function Lnzathash { Lnzathash_v=0 Lnzathash_add "$@" + Lnzathash_end +} +function Lnzathash_end { if (( Lnzathash_v )); then Lnzaathash_end else @@ -351,7 +351,7 @@ function Lstripcom { # give MidnightBSD's laffer1 a bit of csh feeling function setenv { - eval export $1'="$2"' + eval export "\"$1\""'="$2"' } : place customisations below this line diff --git a/src/edit.c b/src/edit.c index 8242563..12fb4eb 100644 --- a/src/edit.c +++ b/src/edit.c @@ -1,10 +1,11 @@ -/* $OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $ */ +/* $OpenBSD: edit.c,v 1.37 2013/01/21 10:13:24 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, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -25,7 +26,9 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.222 2011/10/07 19:45:08 tg Exp $"); +#ifndef MKSH_NO_CMDLINE_EDITING + +__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.265 2013/02/10 19:05:36 tg Exp $"); /* * in later versions we might use libtermcap for this, but since external @@ -58,8 +61,7 @@ static X_chars edchars; #define XCF_FULLPATH BIT(2) /* command completion: store full path */ #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 */ +#define XCF_IS_NOSPACE BIT(4) /* return flag: do not append a space */ static char editmode; static int xx_cols; /* for Emacs mode */ @@ -68,32 +70,34 @@ static char holdbuf[LINE]; /* place to hold last edit buffer */ static int x_getc(void); static void x_putcf(int); +static void x_modified(void); 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 void x_print_expansions(int, char * const *, bool); static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***); -static size_t x_longest_prefix(int, char *const *); +static size_t x_longest_prefix(int, char * const *); +static void x_glob_hlp_add_qchar(char *); +static char *x_glob_hlp_tilde_and_rem_qchar(char *, bool); 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)); static int x_emacs(char *, size_t); -static void x_init_emacs(void); static void x_init_prompt(void); #if !MKSH_S_NOVI static int x_vi(char *, size_t); #endif #define x_flush() shf_flush(shl_out) -#ifdef MKSH_SMALL +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) #define x_putc(c) x_putcf(c) #else #define x_putc(c) shf_putc((c), shl_out) #endif -static int path_order_cmp(const void *aa, const void *bb); +static int path_order_cmp(const void *, const void *); 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, char *, char ***); +static void glob_path(int, const char *, XPtrV *, const char *); +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 *); @@ -102,21 +106,6 @@ static int x_e_rebuildline(const char *); /* +++ generic editing functions +++ */ -/* Called from main */ -void -x_init(void) -{ - /* - * 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; - /* ^W */ - edchars.werase = 027; - x_init_emacs(); -} - /* * read an edited command line */ @@ -237,7 +226,7 @@ x_print_expansions(int nwords, char * const *words, bool is_command) { bool use_copy = false; int prefix_len; - XPtrV l = { NULL, NULL, NULL }; + XPtrV l = { NULL, 0, 0 }; /* * Check if all matches are in the same directory (in this @@ -278,40 +267,109 @@ x_print_expansions(int nwords, char * const *words, bool is_command) XPfree(l); } +/* + * Convert backslash-escaped string to QCHAR-escaped + * string useful for globbing; loses QCHAR unless it + * can squeeze in, eg. by previous loss of backslash + */ +static void +x_glob_hlp_add_qchar(char *cp) +{ + char ch, *dp = cp; + bool escaping = false; + + while ((ch = *cp++)) { + if (ch == '\\' && !escaping) { + escaping = true; + continue; + } + if (escaping || (ch == QCHAR && (cp - dp) > 1)) { + /* + * empirically made list of chars to escape + * for globbing as well as QCHAR itself + */ + switch (ch) { + case QCHAR: + case '$': + case '*': + case '?': + case '[': + case '\\': + case '`': + *dp++ = QCHAR; + break; + } + escaping = false; + } + *dp++ = ch; + } + *dp = '\0'; +} + +/* + * Run tilde expansion on argument string, return the result + * after unescaping; if the flag is set, the original string + * is freed if changed and assumed backslash-escaped, if not + * it is assumed QCHAR-escaped + */ +static char * +x_glob_hlp_tilde_and_rem_qchar(char *s, bool magic_flag) +{ + char ch, *cp, *dp; + + /* + * On the string, check whether we have a tilde expansion, + * and if so, discern "~foo/bar" and "~/baz" from "~blah"; + * if we have a directory part (the former), try to expand + */ + if (*s == '~' && (cp = strchr(s, '/')) != NULL) { + /* ok, so split into "~foo"/"bar" or "~"/"baz" */ + *cp++ = 0; + /* try to expand the tilde */ + if (!(dp = tilde(s + 1))) { + /* nope, revert damage */ + *--cp = '/'; + } else { + /* ok, expand and replace */ + cp = shf_smprintf("%s/%s", dp, cp); + if (magic_flag) + afree(s, ATEMP); + s = cp; + } + } + + /* ... convert it from backslash-escaped via QCHAR-escaped... */ + if (magic_flag) + x_glob_hlp_add_qchar(s); + /* ... to unescaped, for comparison with the matches */ + cp = dp = s; + + while ((ch = *cp++)) { + if (ch == QCHAR && !(ch = *cp++)) + break; + *dp++ = ch; + } + *dp = '\0'; + + return (s); +} + /** * Do file globbing: - * - appends * to (copy of) str if no globbing chars found * - does expansion, checks for no match, etc. * - sets *wordsp to array of matching strings * - returns number of matching strings */ static int -x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp) +x_file_glob(int *flagsp, char *toglob, char ***wordsp) { - char **words; - int nwords, i, idx; - bool escaping; + char **words, *cp; + int nwords; XPtrV w; struct source *s, *sold; /* remove all escaping backward slashes */ - escaping = false; - for (i = 0, idx = 0; toglob[i]; i++) { - if (toglob[i] == '\\' && !escaping) { - escaping = true; - continue; - } - /* specially escape escaped [ or $ or ` for globbing */ - if (escaping && (toglob[i] == '[' || - toglob[i] == '$' || toglob[i] == '`')) - toglob[idx++] = QCHAR; - - toglob[idx] = toglob[i]; - idx++; - if (escaping) - escaping = false; - } - toglob[idx] = '\0'; + x_glob_hlp_add_qchar(toglob); /* * Convert "foo*" (toglob) to an array of strings (words) @@ -326,8 +384,19 @@ x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp) return (0); } source = sold; + afree(s, ATEMP); XPinit(w, 32); - expand(yylval.cp, &w, DOGLOB | DOTILDE | DOMARKDIRS); + cp = yylval.cp; + while (*cp == CHAR || *cp == QCHAR) + cp += 2; + nwords = DOGLOB | DOTILDE | DOMARKDIRS; + if (*cp != EOS) { + /* probably a $FOO expansion */ + *flagsp |= XCF_IS_NOSPACE; + /* this always results in at most one match */ + nwords = 0; + } + expand(yylval.cp, &w, nwords); XPput(w, NULL); words = (char **)XPclose(w); @@ -336,6 +405,9 @@ x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp) if (nwords == 1) { struct stat statb; + /* Expand any tilde and drop all QCHAR for comparison */ + toglob = x_glob_hlp_tilde_and_rem_qchar(toglob, false); + /* * Check if globbing failed (returned glob pattern), * but be careful (e.g. toglob == "ab*" when the file @@ -382,7 +454,7 @@ static int x_command_glob(int flags, char *toglob, char ***wordsp) { char *pat, *fpath; - int nwords; + size_t nwords; XPtrV w; struct block *l; @@ -413,7 +485,7 @@ x_command_glob(int flags, char *toglob, char ***wordsp) /* Sort by basename, then path order */ struct path_order_info *info, *last_info = NULL; char **words = (char **)XPptrv(w); - int i, path_order = 0; + size_t i, path_order = 0; info = (struct path_order_info *) alloc2(nwords, sizeof(struct path_order_info), ATEMP); @@ -435,7 +507,7 @@ x_command_glob(int flags, char *toglob, char ***wordsp) } else { /* Sort and remove duplicate entries */ char **words = (char **)XPptrv(w); - int i, j; + size_t i, j; qsort(words, nwords, sizeof(void *), xstrcmp); for (i = j = 0; i < nwords - 1; i++) { @@ -445,8 +517,7 @@ x_command_glob(int flags, char *toglob, char ***wordsp) afree(words[i], ATEMP); } words[j++] = words[i]; - nwords = j; - w.cur = (void **)&words[j]; + w.len = nwords = j; } XPput(w, NULL); @@ -520,6 +591,8 @@ x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, char **words = NULL; bool is_command; + mkssert(buf != NULL); + len = x_locate_word(buf, buflen, pos, startp, &is_command); if (!((*flagsp) & XCF_COMMAND)) is_command = false; @@ -533,7 +606,6 @@ x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, 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. @@ -544,52 +616,45 @@ x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, /* * 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 '*'). + * '?', or '[') or an extglob, then it is globbed based + * on that value (i.e., without the appended '*'). Same + * for parameter substitutions (as in “cat $HOME/.ss↹”) + * without appending a trailing space (LP: #710539), as + * well as for “~foo” (but not “~foo/”). */ 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 == '[' || + else if (*s == '?' || *s == '*' || *s == '[' || + *s == '$' || /* ?() *() +() @() !() but two already checked */ (s[1] == '(' /*)*/ && (*s == '+' || *s == '@' || *s == '!'))) { - /* just expand based on the extglob */ - saw_glob = true; + /* + * just expand based on the extglob + * or parameter + */ + goto dont_add_glob; } } - 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'; + + if (*toglob == '~' && !vstrchr(toglob, '/')) { + /* neither for '~foo' (but '~foo/bar') */ + *flagsp |= XCF_IS_NOSPACE; + goto dont_add_glob; } + /* append a glob */ + toglob[len] = '*'; + toglob[len + 1] = '\0'; + dont_add_glob: /* * Expand (glob) it now. */ nwords = is_command ? x_command_glob(*flagsp, toglob, &words) : - x_file_glob(*flagsp, toglob, &words); + x_file_glob(flagsp, toglob, &words); afree(toglob, ATEMP); } if (nwords == 0) { @@ -624,6 +689,10 @@ x_longest_prefix(int nwords, char * const * words) prefix_len = j; break; } + /* false for nwords==1 as 0 = words[0][prefix_len] then */ + if (UTFMODE && prefix_len && (words[0][prefix_len] & 0xC0) == 0x80) + while (prefix_len && (words[0][prefix_len] & 0xC0) != 0xC0) + --prefix_len; return (prefix_len); } @@ -728,7 +797,7 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) oldsize = XPsize(*wp); /* mark dirs */ - glob_str(Xstring(xs, xp), wp, 1); + glob_str(Xstring(xs, xp), wp, true); newsize = XPsize(*wp); /* Check that each match is executable... */ @@ -743,7 +812,7 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) } else afree(words[i], ATEMP); } - wp->cur = (void **)&words[j]; + wp->len = j; if (!*sp++) break; @@ -843,22 +912,23 @@ static char *xcp; /* current position */ static char *xep; /* current end */ static char *xbp; /* start of visible portion of input buffer */ static char *xlp; /* last char visible on screen */ -static int x_adj_ok; +static bool x_adj_ok; /* * we use x_adj_done so that functions can tell * whether x_adjust() has been called while they are active. */ -static int x_adj_done; +static int x_adj_done; /* is incremented by x_adjust() */ static int x_col; static int x_displen; static int x_arg; /* general purpose arg */ static bool x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */ -static int xlp_valid; +static bool xlp_valid; /* lastvis pointer was recalculated */ static char **x_histp; /* history position */ static int x_nextcmd; /* for newline-and-next */ +static char **x_histncp; /* saved x_histp for " */ static char *xmp; /* mark pointer */ static unsigned char x_last_command; static unsigned char (*x_tab)[X_TABSZ]; /* key definition */ @@ -885,12 +955,12 @@ static char morec; /* more character at right of window */ static int lastref; /* argument to last refresh() */ static int holdlen; /* length of holdbuf */ #endif -static int prompt_redraw; /* 0 if newline forced after prompt */ +static bool prompt_redraw; /* false if newline forced after prompt */ static int x_ins(const char *); -static void x_delete(int, int); -static int x_bword(void); -static int x_fword(int); +static void x_delete(size_t, bool); +static size_t x_bword(void); +static size_t x_fword(bool); static void x_goto(char *); static void x_bs3(char **); static int x_size_str(char *); @@ -906,8 +976,7 @@ static int x_search_dir(int); 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))); +static char *x_mapin(const char *, Area *); static char *x_mapout(int); static void x_mapout2(int, char **); static void x_print(int, int); @@ -922,6 +991,7 @@ static int x_fold_case(int); #endif static char *x_lastcp(void); static void do_complete(int, Comp_type); +static size_t x_nb2nc(size_t); static int unget_char = -1; @@ -1040,8 +1110,17 @@ static struct x_defbindings const x_defbindings[] = { #endif }; -#ifdef MKSH_SMALL -static void x_modified(void); +static size_t +x_nb2nc(size_t nb) +{ + char *cp; + size_t nc = 0; + + for (cp = xcp; cp < (xcp + nb); ++nc) + cp += utf_ptradj(cp); + return (nc); +} + static void x_modified(void) { @@ -1050,14 +1129,10 @@ x_modified(void) modified = 1; } } + +#ifdef MKSH_SMALL #define XFUNC_VALUE(f) (f) #else -#define x_modified() do { \ - if (!modified) { \ - x_histp = histptr + 1; \ - modified = 1; \ - } \ -} while (/* CONSTCOND */ 0) #define XFUNC_VALUE(f) (f & 0x7F) #endif @@ -1096,8 +1171,8 @@ static void x_init_prompt(void) { x_col = promptlen(prompt); - x_adj_ok = 1; - prompt_redraw = 1; + x_adj_ok = true; + prompt_redraw = true; if (x_col >= xx_cols) x_col %= xx_cols; x_displen = xx_cols - 2 - x_col; @@ -1108,7 +1183,7 @@ x_init_prompt(void) x_col = 0; x_displen = xx_cols - 2; x_e_putc2('\n'); - prompt_redraw = 0; + prompt_redraw = false; } } @@ -1130,10 +1205,13 @@ x_emacs(char *buf, size_t len) xx_cols = x_cols; x_init_prompt(); + x_histncp = NULL; if (x_nextcmd >= 0) { int off = source->line - x_nextcmd; - if (histptr - history >= off) + if (histptr - history >= off) { x_load_hist(histptr - off); + x_histncp = x_histp; + } x_nextcmd = -1; } editmode = 1; @@ -1281,7 +1359,7 @@ x_ins(const char *s) */ xlp_valid = false; x_lastcp(); - x_adj_ok = (xcp >= xlp); + x_adj_ok = tobool(xcp >= xlp); x_zots(cp); /* has x_adjust() been called? */ if (adj == x_adj_done) { @@ -1292,14 +1370,14 @@ x_ins(const char *s) } if (xlp == xep - 1) x_redraw(xx_cols); - x_adj_ok = 1; + x_adj_ok = true; return (0); } static int x_del_back(int c MKSH_A_UNUSED) { - int i = 0; + ssize_t i = 0; if (xcp == xbuf) { x_e_putc2(7); @@ -1316,10 +1394,10 @@ static int x_del_char(int c MKSH_A_UNUSED) { char *cp, *cp2; - int i = 0; + size_t i = 0; cp = xcp; - while (i < x_arg) { + while (i < (size_t)x_arg) { utf_ptradjx(cp, cp2); if (cp2 > xep) break; @@ -1337,9 +1415,9 @@ x_del_char(int c MKSH_A_UNUSED) /* Delete nc chars to the right of the cursor (including cursor position) */ static void -x_delete(int nc, int push) +x_delete(size_t nc, bool push) { - int i, nb, nw; + size_t i, nb, nw; char *cp; if (nc == 0) @@ -1376,7 +1454,7 @@ x_delete(int nc, int push) /* Copies the NUL */ memmove(xcp, xcp + nb, xep - xcp + 1); /* don't redraw */ - x_adj_ok = 0; + x_adj_ok = false; xlp_valid = false; x_zots(xcp); /* @@ -1396,7 +1474,7 @@ x_delete(int nc, int push) x_e_putc2('\b'); } /*x_goto(xcp);*/ - x_adj_ok = 1; + x_adj_ok = true; xlp_valid = false; cp = x_lastcp(); while (cp > xcp) @@ -1423,21 +1501,21 @@ x_mv_bword(int c MKSH_A_UNUSED) static int x_mv_fword(int c MKSH_A_UNUSED) { - x_fword(1); + x_fword(true); return (KSTD); } static int x_del_fword(int c MKSH_A_UNUSED) { - x_delete(x_fword(0), true); + x_delete(x_fword(false), true); return (KSTD); } -static int +static size_t x_bword(void) { - int nc = 0, nb = 0; + size_t nb = 0; char *cp = xcp; if (cp == xbuf) { @@ -1455,16 +1533,14 @@ x_bword(void) } } x_goto(cp); - for (cp = xcp; cp < (xcp + nb); ++nc) - cp += utf_ptradj(cp); - return (nc); + return (x_nb2nc(nb)); } -static int -x_fword(int move) +static size_t +x_fword(bool move) { - int nc = 0; - char *cp = xcp, *cp2; + size_t nc; + char *cp = xcp; if (cp == xep) { x_e_putc2(7); @@ -1476,8 +1552,7 @@ x_fword(int move) while (cp != xep && !is_mfs(*cp)) cp++; } - for (cp2 = xcp; cp2 < cp; ++nc) - cp2 += utf_ptradj(cp2); + nc = x_nb2nc(cp - xcp); if (move) x_goto(cp); return (nc); @@ -1486,7 +1561,9 @@ x_fword(int move) static void x_goto(char *cp) { - if (UTFMODE) + if (cp >= xep) + cp = xep; + else if (UTFMODE) while ((cp > xbuf) && ((*cp & 0xC0) == 0x80)) --cp; if (cp < xbp || cp >= utf_skipcols(xbp, x_displen)) { @@ -1773,7 +1850,10 @@ x_load_hist(char **hp) static int x_nl_next_com(int c MKSH_A_UNUSED) { - x_nextcmd = source->line - (histptr - x_histp) + 1; + if (!x_histncp || (x_histp != x_histncp && x_histp != histptr + 1)) + /* fresh start of ^O */ + x_histncp = x_histp; + x_nextcmd = source->line - (histptr - x_histncp) + 1; return (x_newline('\n')); } @@ -1791,7 +1871,7 @@ static int x_search_hist(int c) { int offset = -1; /* offset of match in xbuf, else -1 */ - char pat[256 + 1]; /* pattern buffer */ + char pat[80 + 1]; /* pattern buffer */ char *p = pat; unsigned char f; @@ -1840,7 +1920,7 @@ x_search_hist(int c) } else if (f == XFUNC_insert) { /* add char to pattern */ /* overflow check... */ - if (p >= &pat[sizeof(pat) - 1]) { + if ((size_t)(p - pat) >= sizeof(pat) - 1) { x_e_putc2(7); continue; } @@ -2003,7 +2083,7 @@ x_redraw(int limit) int i, j, x_trunc = 0; char *cp; - x_adj_ok = 0; + x_adj_ok = false; if (limit == -1) x_e_putc2('\n'); else @@ -2024,7 +2104,6 @@ x_redraw(int limit) x_displen = xx_cols - 2; } xlp_valid = false; - x_lastcp(); x_zots(xbp); if (xbp != xbuf || xep > xlp) limit = xx_cols; @@ -2064,7 +2143,7 @@ x_redraw(int limit) cp = xlp; while (cp > xcp) x_bs3(&cp); - x_adj_ok = 1; + x_adj_ok = true; return; } @@ -2160,20 +2239,18 @@ x_meta2(int c MKSH_A_UNUSED) static int x_kill(int c MKSH_A_UNUSED) { - int col = xcp - xbuf; - int lastcol = xep - xbuf; - int ndel; + size_t col = xcp - xbuf; + size_t lastcol = xep - xbuf; + size_t ndel, narg; - if (x_arg_defaulted) - x_arg = lastcol; - else if (x_arg > lastcol) - x_arg = lastcol; - ndel = x_arg - col; - if (ndel < 0) { - x_goto(xbuf + x_arg); - ndel = -ndel; - } - x_delete(ndel, true); + if (x_arg_defaulted || (narg = x_arg) > lastcol) + narg = lastcol; + if (narg < col) { + x_goto(xbuf + narg); + ndel = col - narg; + } else + ndel = narg - col; + x_delete(x_nb2nc(ndel), true); return (KSTD); } @@ -2182,6 +2259,7 @@ x_push(int nchars) { char *cp; + mkssert(xcp != NULL); strndupx(cp, xcp, nchars, AEDIT); if (killstack[killsp]) afree(killstack[killsp], AEDIT); @@ -2221,7 +2299,7 @@ x_meta_yank(int c MKSH_A_UNUSED) } len = strlen(killstack[killtp]); x_goto(xcp - len); - x_delete(len, false); + x_delete(x_nb2nc(len), false); do { if (killtp == 0) killtp = KILLSIZE - 1; @@ -2303,9 +2381,6 @@ x_mapin(const char *cp, Area *ap) { char *news, *op; - /* for clang's static analyser, the nonnull attribute isn't enough */ - mkssert(cp != NULL); - strdupx(news, cp, ap); op = news; while (*cp) { @@ -2437,7 +2512,7 @@ x_bind(const char *a1, const char *a2, char msg[256]; const char *c = a1; m1 = msg; - while (*c && m1 < (msg + sizeof(msg) - 3)) + while (*c && (size_t)(m1 - msg) < sizeof(msg) - 3) x_mapout2(*c++, &m1); bi_errorf("%s: %s", "too long key sequence", msg); return (1); @@ -2494,38 +2569,17 @@ x_bind(const char *a1, const char *a2, return (0); } -static void -x_init_emacs(void) -{ - int i, j; - - ainit(AEDIT); - x_nextcmd = -1; - - 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++) - for (j = 0; j < X_TABSZ; j++) - x_tab[i][j] = XFUNC_error; - for (i = 0; i < (int)NELEM(x_defbindings); i++) - x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] - = x_defbindings[i].xdb_func; - -#ifndef MKSH_SMALL - 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; -#endif -} - static void bind_if_not_bound(int p, int k, int func) { - /* Has user already bound this key? If so, don't override it */ - if (x_bound[((p) * X_TABSZ + (k)) / 8] & - (1 << (((p) * X_TABSZ + (k)) % 8))) + int t; + + /* + * Has user already bound this key? + * If so, do not override it. + */ + t = p * X_TABSZ + k; + if (x_bound[t >> 3] & (1 << (t & 7))) return; x_tab[p][k] = func; @@ -2541,7 +2595,7 @@ x_set_mark(int c MKSH_A_UNUSED) static int x_kill_region(int c MKSH_A_UNUSED) { - int rsize; + size_t rsize; char *xr; if (xmp == NULL) { @@ -2556,7 +2610,7 @@ x_kill_region(int c MKSH_A_UNUSED) xr = xmp; } x_goto(xr); - x_delete(rsize, true); + x_delete(x_nb2nc(rsize), true); xmp = xr; return (KSTD); } @@ -2649,7 +2703,7 @@ x_expand(int c MKSH_A_UNUSED) return (KSTD); } x_goto(xbuf + start); - x_delete(end - start, false); + x_delete(x_nb2nc(end - start), false); i = 0; while (i < nwords) { @@ -2673,7 +2727,7 @@ do_complete( { char **words; int start, end, nlen, olen, nwords; - bool completed = false; + bool completed; nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf, &start, &end, &words); @@ -2691,30 +2745,56 @@ do_complete( } olen = end - start; nlen = x_longest_prefix(nwords, words); - /* complete */ - if (nwords == 1 || nlen > olen) { - x_goto(xbuf + start); - x_delete(olen, false); - x_escape(words[0], nlen, x_do_ins); - x_adjust(); + if (nwords == 1) { + /* + * always complete single matches; + * any expansion of parameter substitution + * is always at most one result, too + */ completed = true; + } else { + char *unescaped; + + /* make a copy of the original string part */ + strndupx(unescaped, xbuf + start, olen, ATEMP); + + /* expand any tilde and unescape the string for comparison */ + unescaped = x_glob_hlp_tilde_and_rem_qchar(unescaped, true); + + /* + * match iff entire original string is part of the + * longest prefix, implying the latter is at least + * the same size (after unescaping) + */ + completed = !strncmp(words[0], unescaped, strlen(unescaped)); + + afree(unescaped, ATEMP); } + if (type == CT_COMPLIST && nwords > 1) { + /* + * print expansions, since we didn't get back + * just a single match + */ + x_print_expansions(nwords, words, + tobool(flags & XCF_IS_COMMAND)); + } + if (completed) { + /* expand on the command line */ + xmp = NULL; + xcp = xbuf + start; + xep -= olen; + memmove(xcp, xcp + olen, xep - xcp + 1); + x_escape(words[0], nlen, x_do_ins); + } + x_adjust(); /* * 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)) { + !(flags & XCF_IS_NOSPACE)) { x_ins(" "); - completed = true; } - if (type == CT_COMPLIST && !completed) { - x_print_expansions(nwords, words, - tobool(flags & XCF_IS_COMMAND)); - completed = true; - } - if (completed) - x_redraw(0); x_free_words(nwords, words); } @@ -3083,7 +3163,7 @@ x_fold_lower(int c MKSH_A_UNUSED) return (x_fold_case('L')); } -/* Lowercase N(1) words */ +/* Titlecase N(1) words */ static int x_fold_capitalise(int c MKSH_A_UNUSED) { @@ -3095,8 +3175,8 @@ x_fold_capitalise(int c MKSH_A_UNUSED) * x_fold_case - convert word to UPPER/lower/Capital case * * DESCRIPTION: - * This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c - * to UPPER case, lower case or Capitalise words. + * This function is used to implement M-U/M-u, M-L/M-l, M-C/M-c + * to UPPER CASE, lower case or Capitalise Words. * * RETURN VALUE: * None @@ -3239,7 +3319,7 @@ x_mode(bool onoff) if (edchars.quit >= 0) bind_if_not_bound(0, edchars.quit, XFUNC_noop); } else - tcsetattr(tty_fd, TCSADRAIN, &tty_state); + mksh_tcset(tty_fd, &tty_state); } #if !MKSH_S_NOVI @@ -3275,7 +3355,7 @@ static int Forwword(int); static int Backword(int); static int Endword(int); static int grabhist(int, int); -static int grabsearch(int, int, int, char *); +static int grabsearch(int, int, int, const char *); static void redraw_line(bool); static void refresh(int); static int outofwin(void); @@ -3441,11 +3521,12 @@ x_vi(char *buf, size_t len) cur_col -= prompt_trunc; pprompt(prompt, 0); - if (cur_col > x_cols - 3 - MIN_EDIT_SPACE) { - prompt_redraw = cur_col = 0; + if ((mksh_uari_t)cur_col > (mksh_uari_t)x_cols - 3 - MIN_EDIT_SPACE) { + prompt_redraw = false; + cur_col = 0; x_putc('\n'); } else - prompt_redraw = 1; + prompt_redraw = true; pwidth = cur_col; if (!wbuf_len || wbuf_len != x_cols - 3) { @@ -3708,7 +3789,8 @@ vi_hook(int ch) else { locpat[srchlen++] = ch; if (ch < ' ' || ch == 0x7f) { - if (es->linelen + 2 > es->cbufsize) + if ((size_t)es->linelen + 2 > + (size_t)es->cbufsize) vi_error(); es->cbuf[es->linelen++] = '^'; es->cbuf[es->linelen++] = ch ^ '@'; @@ -4299,7 +4381,7 @@ vi_cmd(int argcnt, const char *cmd) break; case '_': { - int inspace; + bool inspace; char *p, *sp; if (histnum(-1) < 0) @@ -4320,12 +4402,12 @@ vi_cmd(int argcnt, const char *cmd) sp = p; } else { sp = p; - inspace = 0; + inspace = false; while (*p) { if (issp(*p)) - inspace = 1; + inspace = true; else if (inspace) { - inspace = 0; + inspace = false; sp = p; } p++; @@ -4340,11 +4422,8 @@ vi_cmd(int argcnt, const char *cmd) argcnt++; p++; } - if (putbuf(" ", 1, 0) != 0) - argcnt = -1; - else if (putbuf(sp, argcnt, 0) != 0) - argcnt = -1; - if (argcnt < 0) { + if (putbuf(" ", 1, 0) != 0 || + putbuf(sp, argcnt, 0) != 0) { if (es->cursor != 0) es->cursor--; return (-1); @@ -4896,7 +4975,7 @@ grabhist(int save, int n) } static int -grabsearch(int save, int start, int fwd, char *pat) +grabsearch(int save, int start, int fwd, const char *pat) { char *hptr; int hist; @@ -4910,8 +4989,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) {} */ - /* XXX should strcmp be strncmp? */ + /* (start != 0 && fwd && match(holdbuf, pat) >= 0) */ if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) { restore_cbuf(); return (0); @@ -5268,7 +5346,7 @@ complete_word(int cmd, int count) * and not a parameter or homedir substitution */ if (match_len > 0 && match[match_len - 1] != '/' && - !(flags & XCF_IS_SUBGLOB)) + !(flags & XCF_IS_NOSPACE)) rval = putbuf(" ", 1, 0); } x_free_words(nwords, words); @@ -5332,33 +5410,49 @@ vi_macro_reset(void) } #endif /* !MKSH_S_NOVI */ +/* called from main.c */ void -x_mkraw(int fd, struct termios *ocb, bool forread) +x_init(void) { - struct termios cb; + int i, j; - if (ocb) - tcgetattr(fd, ocb); - else - ocb = &tty_state; + /* + * 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; + /* ^W */ + edchars.werase = 027; - 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; + /* initialise Emacs command line editing mode */ + ainit(AEDIT); + x_nextcmd = -1; + + 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++) + for (j = 0; j < X_TABSZ; j++) + x_tab[i][j] = XFUNC_error; + for (i = 0; i < (int)NELEM(x_defbindings); i++) + x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] + = x_defbindings[i].xdb_func; + +#ifndef MKSH_SMALL + 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; #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); } + +#ifdef DEBUG_LEAKS +void +x_done(void) +{ + if (x_tab != NULL) + afreeall(AEDIT); +} +#endif +#endif /* !MKSH_NO_CMDLINE_EDITING */ diff --git a/src/eval.c b/src/eval.c index 49e5e6e..4b1f5a0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,7 +1,8 @@ /* $OpenBSD: eval.c,v 1.37 2011/10/11 14:32:43 otto Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.109 2011/10/11 19:06:07 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.136 2013/02/10 23:43:59 tg Exp $"); /* * string expansion @@ -40,7 +41,7 @@ typedef struct Expand { struct shf *shf; /* file */ } u; /* source */ struct tbl *var; /* variable in ${var..} */ - short split; /* split "$@" / call waitlast $() */ + bool split; /* split "$@" / call waitlast $() */ } Expand; #define XBASE 0 /* scanning original */ @@ -57,12 +58,12 @@ typedef struct Expand { #define IFS_NWS 2 /* have seen IFS non-white-space */ static int varsub(Expand *, const char *, const char *, int *, int *); -static int comsub(Expand *, const char *); +static int comsub(Expand *, const char *, int); +static void funsub(struct op *); static char *trimsub(char *, char *, int); -static void glob(char *, XPtrV *, int); +static void glob(char *, XPtrV *, bool); static void globit(XString *, char **, char *, XPtrV *, int); static const char *maybe_expand_tilde(const char *, XString *, char **, int); -static char *tilde(char *); #ifndef MKSH_NOPWNAM static char *homedir(char *); #endif @@ -189,8 +190,8 @@ typedef struct SubType { struct tbl *var; /* variable for ${var..} */ struct SubType *prev; /* old type */ struct SubType *next; /* poped type (to avoid re-allocating) */ + size_t base; /* begin position of expanded word */ short stype; /* [=+-?%#] action after expanded word */ - short base; /* begin position of expanded word */ short f; /* saved value of f (DOPAT, etc) */ uint8_t quotep; /* saved value of quote (for ${..[%#]..}) */ uint8_t quotew; /* saved value of quote (for ${..[+-=]..}) */ @@ -216,8 +217,8 @@ expand(const char *cp, /* input word */ SubType st_head, *st; /* For trailing newlines in COMSUB */ int newlines = 0; - int saw_eq, tilde_ok; - int make_magic; + bool saw_eq, make_magic; + int tilde_ok; size_t len; if (cp == NULL) @@ -239,11 +240,11 @@ expand(const char *cp, /* input word */ type = XBASE; sp = cp; fdo = 0; - saw_eq = 0; + saw_eq = false; /* must be 1/0 */ tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; doblank = 0; - make_magic = 0; + make_magic = false; word = (f&DOBLANK) ? IFS_WS : IFS_WORD; /* clang doesn't know OSUBST comes before CSUBST */ memset(&st_head, 0, sizeof(st_head)); @@ -277,17 +278,27 @@ expand(const char *cp, /* input word */ quote = st->quotew; continue; case COMSUB: + case FUNSUB: tilde_ok = 0; if (f & DONTRUNCOMMAND) { word = IFS_WORD; - *dp++ = '$'; *dp++ = '('; + *dp++ = '$'; + if (c == FUNSUB) { + *dp++ = '{'; + *dp++ = ' '; + } else + *dp++ = '('; while (*sp != '\0') { Xcheck(ds, dp); *dp++ = *sp++; } - *dp++ = ')'; + if (c == FUNSUB) { + *dp++ = ';'; + *dp++ = '}'; + } else + *dp++ = ')'; } else { - type = comsub(&x, sp); + type = comsub(&x, sp, c); if (type == XCOM && (f&DOBLANK)) doblank++; sp = strnul(sp) + 1; @@ -368,16 +379,52 @@ expand(const char *cp, /* input word */ st->stype = stype; st->base = Xsavepos(ds, dp); st->f = f; - st->var = x.var; + if (x.var == &vtemp) { + st->var = tempvar(); + st->var->flag &= ~INTEGER; + /* can't fail here */ + setstr(st->var, + str_val(x.var), + KSH_RETURN_ERROR | 0x4); + } else + st->var = x.var; + st->quotew = st->quotep = quote; /* skip qualifier(s) */ if (stype) sp += slen; switch (stype & 0x17F) { case 0x100 | '#': + { + char *beg, *end; + mksh_ari_t seed; + register uint32_t h; + + beg = wdcopy(sp, ATEMP); + end = beg + (wdscan(sp, CSUBST) - sp); + end[-2] = EOS; + end = wdstrip(beg, 0); + afree(beg, ATEMP); + evaluate(substitute(end, 0), + &seed, KSH_UNWIND_ERROR, true); + /* hash with seed, for now */ + h = seed; + NZATUpdateString(h, + str_val(st->var)); + NZAATFinish(h); x.str = shf_smprintf("%08X", - (unsigned int)hash(str_val(st->var))); + (unsigned int)h); break; + } + case 0x100 | 'Q': + { + struct shf shf; + + shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf); + print_value_quoted(&shf, str_val(st->var)); + x.str = shf_sclose(&shf); + break; + } case '0': { char *beg, *mid, *end, *stg; mksh_ari_t from = 0, num = -1, flen, finc = 0; @@ -470,6 +517,7 @@ expand(const char *cp, /* input word */ /* check for special cases */ d = str_val(st->var); + mkssert(d != NULL); switch (*pat) { case '#': /* anchor at begin */ @@ -574,8 +622,8 @@ expand(const char *cp, /* input word */ case '#': case '%': /* ! DOBLANK,DOBRACE,DOTILDE */ - f = DOPAT | (f&DONTRUNCOMMAND) | - DOTEMP; + f = (f & DONTRUNCOMMAND) | + DOPAT | DOTEMP; st->quotew = quote = 0; /* * Prepend open pattern (so | @@ -706,6 +754,7 @@ expand(const char *cp, /* input word */ case '0': case '/': case 0x100 | '#': + case 0x100 | 'Q': dp = Xrestpos(ds, dp, st->base); type = XSUB; if (f&DOBLANK) @@ -720,19 +769,19 @@ expand(const char *cp, /* input word */ case OPAT: /* open pattern: *(foo|bar) */ /* Next char is the type of pattern */ - make_magic = 1; + make_magic = true; c = *sp++ | 0x80; break; case SPAT: /* pattern separator (|) */ - make_magic = 1; + make_magic = true; c = '|'; break; case CPAT: /* close pattern */ - make_magic = 1; + make_magic = true; c = /*(*/ ')'; break; } @@ -770,6 +819,7 @@ expand(const char *cp, /* input word */ case XARGSEP: type = XARG; quote = 1; + /* FALLTHROUGH */ case XARG: if ((c = *x.str++) == '\0') { /* @@ -855,19 +905,27 @@ expand(const char *cp, /* input word */ p + Xlength(ds, (dp - 1)), fdo | (f & DOMARKDIRS)); else if (fdo & DOGLOB) - glob(p, wp, f & DOMARKDIRS); + glob(p, wp, tobool(f & DOMARKDIRS)); else if ((f & DOPAT) || !(fdo & DOMAGIC)) XPput(*wp, p); else XPput(*wp, debunk(p, p, strlen(p) + 1)); fdo = 0; - saw_eq = 0; + saw_eq = false; tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; - if (c != 0) - Xinit(ds, dp, 128, ATEMP); - } - if (c == 0) + if (c == 0) + return; + Xinit(ds, dp, 128, ATEMP); + } else if (c == 0) { return; + } else if (type == XSUB && ctype(c, C_IFS) && + !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) { + char *p; + + *(p = alloc(1, ATEMP)) = '\0'; + XPput(*wp, p); + type = XSUBMID; + } if (word != IFS_NWS) word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS; } else { @@ -888,7 +946,7 @@ expand(const char *cp, /* input word */ if (!quote) switch (c) { case '[': - case NOT: + case '!': case '-': case ']': /* @@ -910,10 +968,10 @@ expand(const char *cp, /* input word */ *dp++ = MAGIC; } break; - case OBRACE: + case '{': + case '}': case ',': - case CBRACE: - if ((f & DOBRACE) && (c == OBRACE || + if ((f & DOBRACE) && (c == '{' /*}*/ || (fdo & DOBRACE))) { fdo |= DOBRACE|DOMAGIC; *dp++ = MAGIC; @@ -924,7 +982,7 @@ expand(const char *cp, /* input word */ if (!(f & DOTEMP) && !saw_eq && (Flag(FBRACEEXPAND) || (f & DOASNTILDE))) { - saw_eq = 1; + saw_eq = true; tilde_ok = 1; } break; @@ -966,7 +1024,7 @@ expand(const char *cp, /* input word */ quote &= ~2; if (make_magic) { - make_magic = 0; + make_magic = false; fdo |= DOMAGIC | (f & DOGLOB); *dp++ = MAGIC; } else if (ISMAGIC(c)) { @@ -1124,6 +1182,7 @@ varsub(Expand *xp, const char *sp, const char *word, case '0': case '/': case 0x100 | '#': + case 0x100 | 'Q': return (-1); } if (e->loc->argc == 0) { @@ -1133,7 +1192,8 @@ varsub(Expand *xp, const char *sp, const char *word, } else { xp->u.strv = (const char **)e->loc->argv + 1; xp->str = *xp->u.strv++; - xp->split = c == '@'; /* $@ */ + /* $@ */ + xp->split = tobool(c == '@'); state = XARG; } /* POSIX 2009? */ @@ -1151,6 +1211,7 @@ varsub(Expand *xp, const char *sp, const char *word, case '0': case '/': case 0x100 | '#': + case 0x100 | 'Q': return (-1); } XPinit(wv, 32); @@ -1172,7 +1233,8 @@ varsub(Expand *xp, const char *sp, const char *word, XPput(wv, 0); xp->u.strv = (const char **)XPptrv(wv); xp->str = *xp->u.strv++; - xp->split = p[1] == '@'; /* ${foo[@]} */ + /* ${foo[@]} */ + xp->split = tobool(p[1] == '@'); state = XARG; } } else { @@ -1207,7 +1269,8 @@ varsub(Expand *xp, const char *sp, const char *word, if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' || (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */ c == '=' || c == '-' || c == '?' : c == '+'))) || - stype == (0x80 | '0') || stype == (0x100 | '#')) + stype == (0x80 | '0') || stype == (0x100 | '#') || + stype == (0x100 | 'Q')) /* expand word instead of variable value */ state = XBASE; if (Flag(FNOUNSET) && xp->str == null && !zero_ok && @@ -1220,7 +1283,7 @@ varsub(Expand *xp, const char *sp, const char *word, * Run the command in $(...) and read its output. */ static int -comsub(Expand *xp, const char *cp) +comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED) { Source *s, *sold; struct op *t; @@ -1234,10 +1297,15 @@ comsub(Expand *xp, const char *cp) afree(s, ATEMP); source = sold; + UTFMODE = old_utfmode; + if (t == NULL) return (XBASE); - if (t != NULL && t->type == TCOM && + /* no waitlast() unless specifically enabled later */ + xp->split = false; + + if (t->type == TCOM && *t->args == NULL && *t->vars == NULL && t->ioact != NULL) { /* $(ioact; @@ -1250,10 +1318,35 @@ comsub(Expand *xp, const char *cp) SHF_MAPHI|SHF_CLEXEC); if (shf == NULL) errorf("%s: %s %s", name, "can't open", "$() input"); - /* no waitlast() */ - xp->split = 0; + } else if (fn == FUNSUB) { + int ofd1; + struct temp *tf = NULL; + + /* create a temporary file, open for writing */ + maketemp(ATEMP, TT_FUNSUB, &tf); + if (!tf->shf) { + errorf("can't %s temporary file %s: %s", + "create", tf->tffn, cstrerror(errno)); + } + /* save stdout and make the temporary file it */ + ofd1 = savefd(1); + ksh_dup2(shf_fileno(tf->shf), 1, false); + /* + * run tree, with output thrown into the tempfile, + * in a new function block + */ + funsub(t); + subst_exstat = exstat & 0xFF; + /* close the tempfile and restore regular stdout */ + shf_close(tf->shf); + restfd(1, ofd1); + /* now open, unlink and free the tempfile for reading */ + shf = shf_open(tf->tffn, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); + unlink(tf->tffn); + afree(tf, ATEMP); } else { int ofd1, pv[2]; + openpipe(pv); shf = shf_fdopen(pv[0], SHF_RD, NULL); ofd1 = savefd(1); @@ -1261,14 +1354,13 @@ comsub(Expand *xp, const char *cp) ksh_dup2(pv[1], 1, false); close(pv[1]); } - execute(t, XFORK|XXCOM|XPIPEO, NULL); + execute(t, XXCOM | XPIPEO | XFORK, NULL); restfd(1, ofd1); startlast(); /* waitlast() */ - xp->split = 1; + xp->split = true; } - UTFMODE = old_utfmode; xp->u.shf = shf; return (XCOM); } @@ -1276,7 +1368,6 @@ comsub(Expand *xp, const char *cp) /* * perform #pattern and %pattern substitution in ${} */ - static char * trimsub(char *str, char *pat, int how) { @@ -1344,7 +1435,7 @@ trimsub(char *str, char *pat, int how) /* XXX cp not const 'cause slashes are temporarily replaced with NULs... */ static void -glob(char *cp, XPtrV *wp, int markdirs) +glob(char *cp, XPtrV *wp, bool markdirs) { int oldsize = XPsize(*wp); @@ -1365,7 +1456,7 @@ glob(char *cp, XPtrV *wp, int markdirs) * the number of matches found. */ int -glob_str(char *cp, XPtrV *wp, int markdirs) +glob_str(char *cp, XPtrV *wp, bool markdirs) { int oldsize = XPsize(*wp); XString xs; @@ -1410,7 +1501,7 @@ globit(XString *xs, /* dest string */ struct stat lstatb, statb; int stat_done = 0; /* -1: failed, 1 ok */ - if (lstat(Xstring(*xs, xp), &lstatb) < 0) + if (mksh_lstat(Xstring(*xs, xp), &lstatb) < 0) return; /* * special case for systems which strip trailing @@ -1584,7 +1675,7 @@ maybe_expand_tilde(const char *p, XString *dsp, char **dpp, int isassign) * based on a version by Arnold Robbins */ -static char * +char * tilde(char *cp) { char *dp = null; @@ -1638,7 +1729,7 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) char *p; /* search for open brace */ - for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2) + for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != '{' /*}*/; p += 2) ; brace_start = p; @@ -1648,9 +1739,9 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) count = 1; for (p += 2; *p && count; p++) { if (ISMAGIC(*p)) { - if (*++p == OBRACE) + if (*++p == '{' /*}*/) count++; - else if (*p == CBRACE) + else if (*p == /*{*/ '}') --count; else if (*p == ',' && count == 1) comma = p; @@ -1665,7 +1756,7 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) * expansion. } */ if (fdo & DOGLOB) - glob(start, wp, fdo & DOMARKDIRS); + glob(start, wp, tobool(fdo & DOMARKDIRS)); else XPput(*wp, debunk(start, start, end - start)); return; @@ -1681,9 +1772,9 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) count = 1; for (p = brace_start + 2; p != brace_end; p++) { if (ISMAGIC(*p)) { - if (*++p == OBRACE) + if (*++p == '{' /*}*/) count++; - else if ((*p == CBRACE && --count == 0) || + else if ((*p == /*{*/ '}' && --count == 0) || (*p == ',' && count == 1)) { char *news; int l1, l2, l3; @@ -1708,3 +1799,14 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) } return; } + +/* helper function due to setjmp/longjmp woes */ +static void +funsub(struct op *t) +{ + newblock(); + e->type = E_FUNC; + if (!kshsetjmp(e->jbuf)) + execute(t, XXCOM | XERROK, NULL); + popblock(); +} diff --git a/src/exec.c b/src/exec.c index c8dd4c4..9a384db 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,7 +1,8 @@ /* $OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.96 2011/09/07 15:24:14 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.116 2013/02/17 05:40:15 tg Exp $"); #ifndef MKSH_DEFAULT_EXECSHELL #define MKSH_DEFAULT_EXECSHELL "/bin/sh" @@ -31,14 +32,17 @@ __RCSID("$MirOS: src/bin/mksh/exec.c,v 1.96 2011/09/07 15:24:14 tg Exp $"); 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 call_builtin(struct tbl *, const char **, const char *); static int iosetup(struct ioword *, struct tbl *); -static int herein(const char *, int, char **); +static int herein(struct ioword *, 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); +/* XXX: horrible kludge to fit within the framework */ +static char *plain_fmt_entry(char *, size_t, unsigned int, const void *); +static char *select_fmt_entry(char *, size_t, unsigned int, const void *); /* * execute command tree @@ -92,8 +96,7 @@ execute(struct op * volatile t, /* 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) { + herein(t->ioact[0], &cp) == 0 && cp && *cp) { char *sp = cp, *dp; size_t n = ccp - t->vars[0] + 2, z; @@ -234,8 +237,7 @@ execute(struct op * volatile t, */ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); e->type = E_ERRH; - i = sigsetjmp(e->jbuf, 0); - if (i) { + if ((i = kshsetjmp(e->jbuf))) { sigprocmask(SIG_SETMASK, &omask, NULL); quitenv(NULL); unwind(i); @@ -337,10 +339,7 @@ execute(struct op * volatile t, (const char **)eval((const char **)t->vars, DOBLANK | DOGLOB | DOTILDE); e->type = E_LOOP; - while (/* CONSTCOND */ 1) { - i = sigsetjmp(e->jbuf, 0); - if (!i) - break; + while ((i = kshsetjmp(e->jbuf))) { if ((e->flags&EF_BRKCONT_PASS) || (i != LBREAK && i != LCONTIN)) { quitenv(NULL); @@ -375,10 +374,7 @@ execute(struct op * volatile t, case TWHILE: case TUNTIL: e->type = E_LOOP; - while (/* CONSTCOND */ 1) { - i = sigsetjmp(e->jbuf, 0); - if (!i) - break; + while ((i = kshsetjmp(e->jbuf))) { if ((e->flags&EF_BRKCONT_PASS) || (i != LBREAK && i != LCONTIN)) { quitenv(NULL); @@ -464,10 +460,10 @@ execute(struct op * volatile t, if (rv == ENOEXEC) scriptexec(t, (const char **)up); else - errorf("%s: %s", s, strerror(rv)); + errorf("%s: %s", s, cstrerror(rv)); } Break: - exstat = rv; + exstat = rv & 0xFF; if (vp_pipest->flag & INT_L) { unset(vp_pipest, 1); vp_pipest->flag = DEFINED | ISSET | INTEGER | RDONLY | @@ -482,9 +478,14 @@ execute(struct op * volatile t, unwind(LEXIT); if (rv != 0 && !(flags & XERROK) && (xerrok == NULL || !*xerrok)) { - trapsig(ksh_SIGERR); - if (Flag(FERREXIT)) - unwind(LERROR); + if (Flag(FERREXIT) & 0x80) { + /* inside eval */ + Flag(FERREXIT) = 0; + } else { + trapsig(ksh_SIGERR); + if (Flag(FERREXIT)) + unwind(LERROR); + } } return (rv); } @@ -504,7 +505,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, /* Must be static (XXX but why?) */ static struct op texec; int type_flags; - int keepasn_ok; + bool keepasn_ok; int fcflags = FC_BI|FC_FUNC|FC_PATH; bool bourne_function_call = false; struct block *l_expand, *l_assign; @@ -536,7 +537,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, * FOO=bar command FOO is neither kept nor exported * PATH=... foobar use new PATH in foobar search */ - keepasn_ok = 1; + keepasn_ok = true; while (tp && tp->type == CSHELL) { /* undo effects of command */ fcflags = FC_BI|FC_FUNC|FC_PATH; @@ -583,7 +584,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, * POSIX says special builtins lose their status * if accessed using command. */ - keepasn_ok = 0; + keepasn_ok = false; if (!ap[0]) { /* ensure command with no args exits with 0 */ subst_exstat = 0; @@ -607,11 +608,20 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, else /* go on, use the builtin */ break; +#endif +#if !defined(MKSH_SMALL) + } else if (tp->val.f == c_trap) { + t->u.evalflags &= ~DOTCOMEXEC; + break; #endif } else break; tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC)); } +#if !defined(MKSH_SMALL) + if (t->u.evalflags & DOTCOMEXEC) + flags |= XEXEC; +#endif l_expand = e->loc; if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN)))) type_flags = 0; @@ -666,7 +676,11 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, /* shell built-in */ case CSHELL: - rv = call_builtin(tp, (const char **)ap); + rv = call_builtin(tp, (const char **)ap, null); + if (!keepasn_ok && tp->val.f == c_shift) { + l_expand->argc = l_assign->argc; + l_expand->argv = l_assign->argv; + } break; /* function call */ @@ -682,14 +696,14 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, rv = (tp->u2.errnov == ENOENT) ? 127 : 126; warningf(true, "%s: %s %s: %s", cp, "can't find", "function definition file", - strerror(tp->u2.errnov)); + cstrerror(tp->u2.errnov)); break; } - if (include(tp->u.fpath, 0, NULL, 0) < 0) { + if (include(tp->u.fpath, 0, NULL, false) < 0) { rv = errno; warningf(true, "%s: %s %s %s: %s", cp, "can't open", "function definition file", - tp->u.fpath, strerror(rv)); + tp->u.fpath, cstrerror(rv)); rv = 127; break; } @@ -733,10 +747,8 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, tp->flag |= FINUSE; e->type = E_FUNC; - i = sigsetjmp(e->jbuf, 0); - if (i == 0) { - /* seems odd to pass XERROK here, but AT&T ksh does */ - exstat = execute(tp->val.t, flags & XERROK, xerrok); + if (!(i = kshsetjmp(e->jbuf))) { + execute(tp->val.t, flags & XERROK, NULL); i = LRETURN; } kshname = old_kshname; @@ -757,7 +769,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, switch (i) { case LRETURN: case LERROR: - rv = exstat; + rv = exstat & 0xFF; break; case LINTR: case LEXIT: @@ -784,7 +796,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, } else { rv = 126; warningf(true, "%s: %s: %s", cp, "can't execute", - strerror(tp->u2.errnov)); + cstrerror(tp->u2.errnov)); } break; } @@ -817,7 +829,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap, } Leave: if (flags & XEXEC) { - exstat = rv; + exstat = rv & 0xFF; unwind(LLEAVE); } return (rv); @@ -919,7 +931,7 @@ scriptexec(struct op *tp, const char **ap) execve(args.rw[0], args.rw, cap.rw); /* report both the programme that was run and the bogus interpreter */ - errorf("%s: %s: %s", tp->str, sh, strerror(errno)); + errorf("%s: %s: %s", tp->str, sh, cstrerror(errno)); } int @@ -928,9 +940,7 @@ shcomexec(const char **wp) struct tbl *tp; tp = ktsearch(&builtins, *wp, hash(*wp)); - if (tp == NULL) - internal_errorf("%s: %s", "shcomexec", *wp); - return (call_builtin(tp, wp)); + return (call_builtin(tp, wp, "shcomexec")); } /* @@ -979,6 +989,8 @@ define(const char *name, struct op *t) while (/* CONSTCOND */ 1) { tp = findfunc(name, nhash, true); + /* because findfunc:create=true */ + mkssert(tp != NULL); if (tp->flag & ISSET) was_set = true; @@ -1245,10 +1257,12 @@ search_path(const char *name, const char *lpath, } static int -call_builtin(struct tbl *tp, const char **wp) +call_builtin(struct tbl *tp, const char **wp, const char *where) { int rv; + if (!tp) + internal_errorf("%s: %s", where, wp[0]); builtin_argv0 = wp[0]; builtin_flag = tp->flag; shf_reopen(1, SHF_WR, shl_stdout); @@ -1271,7 +1285,8 @@ iosetup(struct ioword *iop, struct tbl *tp) int u = -1; char *cp = iop->name; int iotype = iop->flag & IOTYPE; - int do_open = 1, do_close = 0, flags = 0; + bool do_open = true, do_close = false; + int flags = 0; struct ioword iotmp; struct stat statb; @@ -1313,20 +1328,20 @@ iosetup(struct ioword *iop, struct tbl *tp) break; case IOHERE: - do_open = 0; + do_open = false; /* herein() returns -2 if error has been printed */ - u = herein(iop->heredoc, iop->flag & IOEVAL, NULL); + u = herein(iop, NULL); /* cp may have wrong name */ break; case IODUP: { const char *emsg; - do_open = 0; + do_open = false; if (*cp == '-' && !cp[1]) { /* prevent error return below */ u = 1009; - do_close = 1; + do_close = true; } else if ((u = check_fd(cp, X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK), &emsg)) < 0) { @@ -1338,7 +1353,7 @@ iosetup(struct ioword *iop, struct tbl *tp) /* "dup from" == "dup to" */ return (0); break; - } + } } if (do_open) { @@ -1355,7 +1370,7 @@ iosetup(struct ioword *iop, struct tbl *tp) warningf(true, "can't %s %s: %s", iotype == IODUP ? "dup" : (iotype == IOREAD || iotype == IOHERE) ? - "open" : "create", cp, strerror(u)); + "open" : "create", cp, cstrerror(u)); } return (-1); } @@ -1379,13 +1394,13 @@ iosetup(struct ioword *iop, struct tbl *tp) close(iop->unit); else if (u != iop->unit) { if (ksh_dup2(u, iop->unit, true) < 0) { - int ev; + int eno; - ev = errno; + eno = errno; warningf(true, "%s %s %s", "can't finish (dup) redirection", snptreef(NULL, 32, "%R", &iotmp), - strerror(ev)); + cstrerror(eno)); if (iotype != IODUP) close(u); return (-1); @@ -1419,12 +1434,12 @@ iosetup(struct ioword *iop, struct tbl *tp) static int hereinval(const char *content, int sub, char **resbuf, struct shf *shf) { - const char *ccp; + const char * volatile ccp = content; struct source *s, *osource; osource = source; newenv(E_ERRH); - if (sigsetjmp(e->jbuf, 0)) { + if (kshsetjmp(e->jbuf)) { source = osource; quitenv(shf); /* special to iosetup(): don't print error */ @@ -1433,14 +1448,13 @@ hereinval(const char *content, int sub, char **resbuf, struct shf *shf) if (sub) { /* do substitutions on the content of heredoc */ s = pushs(SSTRING, ATEMP); - s->start = s->str = content; + s->start = s->str = ccp; source = s; - if (yylex(ONEWORD|HEREDOC) != LWORD) + if (yylex(sub) != LWORD) internal_errorf("%s: %s", "herein", "yylex"); source = osource; ccp = evalstr(yylval.cp, 0); - } else - ccp = content; + } if (resbuf == NULL) shf_puts(ccp, shf); @@ -1452,7 +1466,7 @@ hereinval(const char *content, int sub, char **resbuf, struct shf *shf) } static int -herein(const char *content, int sub, char **resbuf) +herein(struct ioword *iop, char **resbuf) { int fd = -1; struct shf *shf; @@ -1460,32 +1474,35 @@ herein(const char *content, int sub, char **resbuf) int i; /* ksh -c 'cat << EOF' can cause this... */ - if (content == NULL) { + if (iop->heredoc == NULL) { warningf(true, "%s missing", "here document"); /* special to iosetup(): don't print error */ return (-2); } + /* lexer substitution flags */ + i = (iop->flag & IOEVAL) ? (ONEWORD | HEREDOC) : 0; + /* skip all the fd setup if we just want the value */ if (resbuf != NULL) - return (hereinval(content, sub, resbuf, NULL)); + return (hereinval(iop->heredoc, i, 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) { + if (!(shf = h->shf) || (fd = open(h->tffn, O_RDONLY, 0)) < 0) { i = errno; warningf(true, "can't %s temporary file %s: %s", - !shf ? "create" : "open", h->name, strerror(i)); + !shf ? "create" : "open", h->tffn, cstrerror(i)); if (shf) shf_close(shf); /* special to iosetup(): don't print error */ return (-2); } - if (hereinval(content, sub, NULL, shf) == -2) { + if (hereinval(iop->heredoc, i, NULL, shf) == -2) { close(fd); /* special to iosetup(): don't print error */ return (-2); @@ -1494,7 +1511,8 @@ herein(const char *content, int sub, char **resbuf) if (shf_close(shf) == EOF) { i = errno; close(fd); - warningf(true, "%s: %s: %s", "write", h->name, strerror(i)); + warningf(true, "can't %s temporary file %s: %s", + "write", h->tffn, cstrerror(i)); /* special to iosetup(): don't print error */ return (-2); } @@ -1527,14 +1545,14 @@ do_selectargs(const char **ap, bool print_menu) if (print_menu || !*str_val(global("REPLY"))) pr_menu(ap); shellf("%s", str_val(global("PS3"))); - if (call_builtin(findcom("read", FC_BI), read_args)) + if (call_builtin(findcom("read", FC_BI), read_args, Tselect)) return (NULL); s = str_val(global("REPLY")); if (*s) { getn(s, &i); return ((i >= 1 && i <= argct) ? ap[i - 1] : null); } - print_menu = 1; + print_menu = true; } } @@ -1543,16 +1561,14 @@ struct select_menu_info { int num_width; }; -static char *select_fmt_entry(char *, size_t, int, const void *); - /* format a single select menu item */ static char * -select_fmt_entry(char *buf, size_t buflen, int i, const void *arg) +select_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg) { const struct select_menu_info *smi = (const struct select_menu_info *)arg; - shf_snprintf(buf, buflen, "%*d) %s", + shf_snprintf(buf, buflen, "%*u) %s", smi->num_width, i + 1, smi->args[i]); return (buf); } @@ -1560,13 +1576,13 @@ select_fmt_entry(char *buf, size_t buflen, int i, const void *arg) /* * print a select style menu */ -int +void pr_menu(const char * const *ap) { struct select_menu_info smi; const char * const *pp; size_t acols = 0, aocts = 0, i; - int n; + unsigned int n; /* * width/column calculations were done once and saved, but this @@ -1598,25 +1614,20 @@ pr_menu(const char * const *ap) print_columns(shl_out, n, select_fmt_entry, (void *)&smi, smi.num_width + 2 + aocts, smi.num_width + 2 + acols, true); - - return (n); } -/* XXX: horrible kludge to fit within the framework */ -static char *plain_fmt_entry(char *, size_t, int, const void *); - static char * -plain_fmt_entry(char *buf, size_t buflen, int i, const void *arg) +plain_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg) { strlcpy(buf, ((const char * const *)arg)[i], buflen); return (buf); } -int +void pr_list(char * const *ap) { size_t acols = 0, aocts = 0, i; - int n; + unsigned int n; char * const *pp; for (n = 0, pp = ap; *pp; n++, pp++) { @@ -1630,8 +1641,6 @@ pr_list(char * const *ap) print_columns(shl_out, n, plain_fmt_entry, (const void *)ap, aocts, acols, false); - - return (n); } /* @@ -1664,9 +1673,10 @@ dbteste_isa(Test_env *te, Test_meta meta) char buf[8]; char *q = buf; - for (p = *te->pos.wp; - *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2) - *q++ = p[1]; + p = *te->pos.wp; + while (*p++ == CHAR && + (size_t)(q - buf) < sizeof(buf) - 1) + *q++ = *p++; *q = '\0'; ret = test_isop(meta, buf); } diff --git a/src/expr.c b/src/expr.c index 3c8252d..321c31e 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1,7 +1,8 @@ /* $OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +23,21 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.49 2011/09/07 15:24:14 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.61 2013/02/15 18:36:48 tg Exp $"); + +#if !HAVE_SILENT_IDIVWRAPV +#if !defined(MKSH_LEGACY_MODE) || HAVE_LONG_32BIT +#define IDIVWRAPV_VL (mksh_uari_t)0x80000000UL +#define IDIVWRAPV_VR (mksh_uari_t)0xFFFFFFFFUL +#elif HAVE_LONG_64BIT +#define IDIVWRAPV_VL (mksh_uari_t)0x8000000000000000UL +#define IDIVWRAPV_VR (mksh_uari_t)0xFFFFFFFFFFFFFFFFUL +#else +# warning "cannot guarantee integer division wraparound" +#undef HAVE_SILENT_IDIVWRAPV +#define HAVE_SILENT_IDIVWRAPV 1 +#endif +#endif /* The order of these enums is constrained by the order of opinfo[] */ enum token { @@ -124,29 +139,29 @@ static const struct opinfo opinfo[] = { { "", 0, P_PRIMARY } }; -typedef struct expr_state Expr_state; -struct expr_state { - const char *expression; /* expression being evaluated */ - const char *tokp; /* lexical position */ - struct tbl *val; /* value from token() */ - struct tbl *evaling; /* variable that is being recursively - * expanded (EXPRINEVAL flag set) */ - int noassign; /* don't do assigns (for ?:,&&,||) */ - enum token tok; /* token from token() */ - bool arith; /* evaluating an $(()) expression? */ - bool natural; /* unsigned arithmetic calculation */ -}; +typedef struct expr_state { + /* expression being evaluated */ + const char *expression; + /* lexical position */ + const char *tokp; + /* value from token() */ + struct tbl *val; + /* variable that is being recursively expanded (EXPRINEVAL flag set) */ + struct tbl *evaling; + /* token from token() */ + enum token tok; + /* don't do assignments (for ?:, &&, ||) */ + short noassign; + /* evaluating an $(()) expression? */ + bool arith; + /* unsigned arithmetic calculation */ + bool natural; +} Expr_state; #define bivui(x, op, y) (es->natural ? \ - (mksh_ari_t)((x)->val.u op (y)->val.u) : \ - (mksh_ari_t)((x)->val.i op (y)->val.i) \ + (mksh_uari_t)((x)->val.u op (y)->val.u) : \ + (mksh_uari_t)((x)->val.i op (y)->val.i) \ ) -#define stvui(x, n) do { \ - if (es->natural) \ - (x)->val.u = (n); \ - else \ - (x)->val.i = (n); \ -} while (/* CONSTCOND */ 0) enum error_type { ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE, @@ -159,7 +174,6 @@ static struct tbl *evalexpr(Expr_state *, int); static void exprtoken(Expr_state *); static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool); static void assign_check(Expr_state *, enum token, struct tbl *); -static struct tbl *tempvar(void); static struct tbl *intvar(Expr_state *, struct tbl *); /* @@ -191,15 +205,13 @@ v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok, int i; /* save state to allow recursive calls */ + memset(&curstate, 0, sizeof(curstate)); curstate.expression = curstate.tokp = expr; - curstate.noassign = 0; + curstate.tok = BAD; curstate.arith = arith; - curstate.evaling = NULL; - curstate.natural = false; newenv(E_ERRH); - i = sigsetjmp(e->jbuf, 0); - if (i) { + if ((i = kshsetjmp(e->jbuf))) { /* Clear EXPRINEVAL in of any variables we were playing with */ if (curstate.evaling) curstate.evaling->flag &= ~EXPRINEVAL; @@ -284,7 +296,7 @@ evalerr(Expr_state *es, enum error_type type, const char *str) case ET_RDONLY: warningf(true, "%s: %s %s", - es->expression, str, "applied to read only variable"); + es->expression, str, "applied to read-only variable"); break; default: /* keep gcc happy */ @@ -300,7 +312,7 @@ evalexpr(Expr_state *es, int prec) { struct tbl *vl, *vr = NULL, *vasn; enum token op; - mksh_ari_t res = 0; + mksh_uari_t res = 0; if (prec == P_PRIMARY) { op = es->tok; @@ -343,10 +355,12 @@ evalexpr(Expr_state *es, int prec) op = es->tok) { exprtoken(es); vasn = vl; - if (op != O_ASN) /* vl may not have a value yet */ + if (op != O_ASN) + /* vl may not have a value yet */ vl = intvar(es, vl); if (IS_ASSIGNOP(op)) { - assign_check(es, op, vasn); + if (!es->noassign) + assign_check(es, op, vasn); vr = intvar(es, evalexpr(es, P_ASSIGN)); } else if (op != O_TERN && op != O_LAND && op != O_LOR) vr = intvar(es, evalexpr(es, prec - 1)); @@ -364,11 +378,36 @@ evalexpr(Expr_state *es, int prec) break; case O_DIV: case O_DIVASN: - res = bivui(vl, /, vr); +#if !HAVE_SILENT_IDIVWRAPV + /* + * we are doing the comparisons here for the + * signed arithmetics (!es->natural) case, + * but the exact value checks and the bypass + * case assignments are done unsignedly as + * several compilers bitch around otherwise + */ + if (!es->natural && + vl->val.u == IDIVWRAPV_VL && + vr->val.u == IDIVWRAPV_VR) { + /* -2147483648 / -1 = 2147483648 */ + /* this ^ is really (1 << 31) though */ + res = IDIVWRAPV_VL; + } else +#endif + res = bivui(vl, /, vr); break; case O_MOD: case O_MODASN: - res = bivui(vl, %, vr); +#if !HAVE_SILENT_IDIVWRAPV + /* see O_DIV / O_DIVASN for the reason behind this */ + if (!es->natural && + vl->val.u == IDIVWRAPV_VL && + vr->val.u == IDIVWRAPV_VR) { + /* -2147483648 % -1 = 0 */ + res = 0; + } else +#endif + res = bivui(vl, %, vr); break; case O_PLUS: case O_PLUSASN: @@ -453,23 +492,23 @@ evalexpr(Expr_state *es, int prec) } break; case O_ASN: - res = vr->val.i; + res = vr->val.u; break; case O_COMMA: - res = vr->val.i; + res = vr->val.u; break; } if (IS_ASSIGNOP(op)) { - stvui(vr, res); + vr->val.u = res; if (!es->noassign) { if (vasn->flag & INTEGER) setint_v(vasn, vr, es->arith); else - setint(vasn, res); + setint(vasn, (mksh_ari_t)res); } vl = vr; } else if (op != O_TERN) - stvui(vl, res); + vl->val.u = res; } return (vl); } @@ -597,7 +636,8 @@ do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix) setint_v(vasn, vl, es->arith); else setint(vasn, vl->val.i); - if (!is_prefix) /* undo the inc/dec */ + if (!is_prefix) + /* undo the increment/decrement */ vl->val.i = oval; return (vl); @@ -606,14 +646,14 @@ do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix) static void assign_check(Expr_state *es, enum token op, struct tbl *vasn) { - if (es->tok == END || + if (es->tok == END || !vasn || (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))) evalerr(es, ET_LVALUE, opinfo[(int)op].name); else if (vasn->flag & RDONLY) evalerr(es, ET_RDONLY, opinfo[(int)op].name); } -static struct tbl * +struct tbl * tempvar(void) { struct tbl *vp; @@ -804,7 +844,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.10 2010/12/11 16:05:03 tg Exp $"); +__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.11 2012/09/01 23:46:43 tg Exp $"); int utf_wcwidth(unsigned int c) @@ -813,69 +853,71 @@ utf_wcwidth(unsigned int c) unsigned short first; unsigned short last; } comb[] = { - /* Unicode 6.0.0 BMP */ + /* Unicode 6.1.0 BMP */ { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, - { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, { 0x0610, 0x061A }, + { 0x05C7, 0x05C7 }, { 0x0600, 0x0604 }, { 0x0610, 0x061A }, { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DD }, { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 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, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 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 }, { 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 }, { 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 }, + { 0x0859, 0x085B }, { 0x08E4, 0x08FE }, { 0x0900, 0x0902 }, + { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, + { 0x094D, 0x094D }, { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, + { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, + { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, + { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, + { 0x0A4B, 0x0A4D }, { 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 }, + { 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 }, { 0x1B80, 0x1B81 }, + { 0x1BA2, 0x1BA5 }, { 0x1BA8, 0x1BA9 }, { 0x1BAB, 0x1BAB }, + { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 }, { 0x1BED, 0x1BED }, + { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 }, { 0x1C36, 0x1C37 }, + { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 }, { 0x1CE2, 0x1CE8 }, + { 0x1CED, 0x1CED }, { 0x1CF4, 0x1CF4 }, { 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 }, + { 0x302A, 0x302D }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 }, + { 0xA674, 0xA67D }, { 0xA69F, 0xA69F }, { 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 }, { 0xAAEC, 0xAAED }, { 0xAAF6, 0xAAF6 }, { 0xABE5, 0xABE5 }, { 0xABE8, 0xABE8 }, { 0xABED, 0xABED }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE26 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB } diff --git a/src/funcs.c b/src/funcs.c index 23f45de..e23ec8b 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -5,7 +5,7 @@ /*- * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, - * 2010, 2011 + * 2010, 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -38,7 +38,7 @@ #endif #endif -__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.197 2011/09/07 15:24:15 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.238 2013/02/18 22:47:32 tg Exp $"); #if HAVE_KILLPG /* @@ -60,9 +60,16 @@ __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.197 2011/09/07 15:24:15 tg Exp $"); #define c_ulimit c_true #endif -#if defined(ANDROID) -static int c_android_lsmod(const char **); -#endif +/* getn() that prints error */ +static int +bi_getn(const char *as, int *ai) +{ + int rv; + + if (!(rv = getn(as, ai))) + bi_errorf("%s: %s", as, "bad number"); + return (rv); +} static int c_true(const char **wp MKSH_A_UNUSED) @@ -104,7 +111,7 @@ const struct builtin mkshbuiltins[] = { {"+true", c_true}, {"ulimit", c_ulimit}, {"+umask", c_umask}, - {"*=unset", c_unset}, + {Tsgunset, c_unset}, /* no =: AT&T manual wrong */ {Tpalias, c_alias}, {"+cd", c_cd}, @@ -112,7 +119,7 @@ const struct builtin mkshbuiltins[] = { {"chdir", c_cd}, {"+command", c_command}, {"echo", c_print}, - {"*=export", c_typeset}, + {Tsgexport, c_typeset}, {"+fc", c_fc}, {"+getopts", c_getopts}, {"=global", c_typeset}, @@ -124,7 +131,7 @@ const struct builtin mkshbuiltins[] = { {"printf", c_printf}, #endif {"pwd", c_pwd}, - {"*=readonly", c_typeset}, + {Tsgreadonly, c_typeset}, {T_typeset, c_typeset}, {Tpunalias, c_unalias}, {"whence", c_whence}, @@ -132,7 +139,9 @@ const struct builtin mkshbuiltins[] = { {"+bg", c_fgbg}, {"+fg", c_fgbg}, #endif +#ifndef MKSH_NO_CMDLINE_EDITING {"bind", c_bind}, +#endif {"cat", c_cat}, #if HAVE_MKNOD {"mknod", c_mknod}, @@ -145,9 +154,6 @@ const struct builtin mkshbuiltins[] = { #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} }; @@ -212,10 +218,9 @@ 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 *, size_t, int, const void *); +static char *kill_fmt_entry(char *, size_t, unsigned int, const void *); static void p_time(struct shf *, bool, long, int, int, - const char *, const char *) - MKSH_A_NONNULL((__nonnull__ (6, 7))); + const char *, const char *); int c_pwd(const char **wp) @@ -248,7 +253,7 @@ c_pwd(const char **wp) p = NULL; if (!p && !(p = allocd = ksh_get_wd())) { bi_errorf("%s: %s", "can't determine current directory", - strerror(errno)); + cstrerror(errno)); return (1); } shprintf("%s\n", p); @@ -269,7 +274,7 @@ c_print(const char **wp) #define PO_HIST BIT(3) /* print to history instead of stdout */ #define PO_COPROC BIT(4) /* printing to coprocess: block SIGPIPE */ int fd = 1, c; - int flags = PO_EXPAND|PO_NL; + int flags = PO_EXPAND | PO_NL; const char *s, *emsg; XString xs; char *xp; @@ -277,7 +282,21 @@ c_print(const char **wp) if (wp[0][0] == 'e') { /* echo builtin */ wp++; - if (Flag(FPOSIX) || Flag(FSH) || Flag(FAS_BUILTIN)) { +#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT + if (Flag(FSH)) { + /* + * MidnightBSD /bin/sh needs a BSD echo, that is, + * one that supports -e but does not enable it by + * default + */ + flags = PO_NL; + } +#endif + if (Flag(FPOSIX) || +#ifndef MKSH_MIDNIGHTBSD01ASH_COMPAT + Flag(FSH) || +#endif + Flag(FAS_BUILTIN)) { /* Debian Policy 10.4 compliant "echo" builtin */ if (*wp && !strcmp(*wp, "-n")) { /* we recognise "-n" only as the first arg */ @@ -547,7 +566,7 @@ c_whence(const char **wp) Talias); if (!iam_whence && !vflag) shprintf("%s %s=", Talias, id); - print_value_quoted(tp->val.s); + print_value_quoted(shl_stdout, tp->val.s); break; case CFUNC: if (vflag) { @@ -610,15 +629,18 @@ c_command(const char **wp) } /* typeset, global, export, and readonly */ +static void c_typeset_vardump(struct tbl *, uint32_t, int, bool, bool); +static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool, + bool); int c_typeset(const char **wp) { - struct block *l; struct tbl *vp, **p; uint32_t fset = 0, fclr = 0, flag; - int thing = 0, field, base, optc; + int thing = 0, field = 0, base = 0, i; + struct block *l; const char *opts; - const char *fieldstr, *basestr; + const char *fieldstr = NULL, *basestr = NULL; bool localv = false, func = false, pflag = false, istset = true; switch (**wp) { @@ -649,7 +671,6 @@ c_typeset(const char **wp) /* see comment below regarding possible opions */ opts = istset ? "L#R#UZ#afi#lnprtux" : "p"; - fieldstr = basestr = NULL; builtin_opt.flags |= GF_PLUSOPT; /* * AT&T ksh seems to have 0-9 as options which are multiplied @@ -660,9 +681,9 @@ c_typeset(const char **wp) * Here, the number must follow the RLZi option, but is optional * (see the # kludge in ksh_getopt()). */ - while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) { + while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) { flag = 0; - switch (optc) { + switch (i) { case 'L': flag = LJUST; fieldstr = builtin_opt.optarg; @@ -725,6 +746,8 @@ c_typeset(const char **wp) flag = EXPORT; break; case '?': + errout: + set_refflag = SRF_NOP; return (1); } if (builtin_opt.info & GI_PLUS) { @@ -738,12 +761,12 @@ c_typeset(const char **wp) } } - field = 0; if (fieldstr && !bi_getn(fieldstr, &field)) - return (1); - base = 0; - if (basestr && !bi_getn(basestr, &base)) - return (1); + goto errout; + if (basestr && (!bi_getn(basestr, &base) || base < 1 || base > 36)) { + bi_errorf("%s: %s", "bad integer base", basestr); + goto errout; + } if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] && (wp[builtin_opt.optind][0] == '-' || @@ -756,8 +779,7 @@ c_typeset(const char **wp) 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 = SRF_NOP; - return (1); + goto errout; } if (wp[builtin_opt.optind]) { /* @@ -787,8 +809,10 @@ c_typeset(const char **wp) } /* set variables and attributes */ - if (wp[builtin_opt.optind]) { - int i, rv = 0; + if (wp[builtin_opt.optind] && + /* not "typeset -p varname" */ + !(!func && pflag && !(fset | fclr))) { + int rv = 0; struct tbl *f; if (localv && !func) @@ -813,14 +837,14 @@ c_typeset(const char **wp) } } else if (!typeset(wp[i], fset, fclr, field, base)) { bi_errorf("%s: %s", wp[i], "not identifier"); - set_refflag = SRF_NOP; - return (1); + goto errout; } } set_refflag = SRF_NOP; return (rv); } + set_refflag = SRF_NOP; /* list variables and attributes */ /* no difference at this point.. */ @@ -839,138 +863,138 @@ c_typeset(const char **wp) shf_putc('\n', shl_stdout); } } - } else { - for (l = e->loc; l; l = l->next) { - for (p = ktsort(&l->vars); (vp = *p++); ) { - struct tbl *tvp; - bool any_set = false; - /* - * See if the parameter is set (for arrays, if any - * element is set). - */ - for (tvp = vp; tvp; tvp = tvp->u.array) - if (tvp->flag & ISSET) { - any_set = true; - break; - } - - /* - * Check attributes - note that all array elements - * have (should have?) the same attributes, so checking - * the first is sufficient. - * - * Report an unset param only if the user has - * explicitly given it some attribute (like export); - * otherwise, after "echo $FOO", we would report FOO... - */ - if (!any_set && !(vp->flag & USERATTRIB)) - continue; - if (flag && (vp->flag & flag) == 0) - continue; - for (; vp; vp = vp->u.array) { - /* - * Ignore array elements that aren't - * set unless there are no set elements, - * in which case the first is reported on - */ - if ((vp->flag&ARRAY) && any_set && - !(vp->flag & ISSET)) - continue; - /* no arguments */ - if (thing == 0 && flag == 0) { - /* - * AT&T ksh prints things - * like export, integer, - * leftadj, zerofill, etc., - * but POSIX says must - * be suitable for re-entry... - */ - shf_puts("typeset ", shl_stdout); - if (((vp->flag&(ARRAY|ASSOC))==ASSOC)) - shprintf("%s ", "-n"); - if ((vp->flag&INTEGER)) - shprintf("%s ", "-i"); - if ((vp->flag&EXPORT)) - shprintf("%s ", "-x"); - if ((vp->flag&RDONLY)) - shprintf("%s ", "-r"); - if ((vp->flag&TRACE)) - 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)) - shprintf("%s ", "-Z"); - if ((vp->flag&LCASEV)) - shprintf("%s ", "-l"); - if ((vp->flag&UCASEV_AL)) - shprintf("%s ", "-u"); - if ((vp->flag&INT_U)) - 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... - */ - if ((vp->flag & - (INTEGER|LJUST|RJUST)) == - INTEGER) - shf_puts(s, shl_stdout); - else - print_value_quoted(s); - } - shf_putc('\n', shl_stdout); - if (vp->flag & ARRAY) - break; - } else { - if (pflag) - shf_puts(istset ? - "typeset " : - (flag & EXPORT) ? - "export " : - "readonly ", - shl_stdout); - if ((vp->flag&ARRAY) && any_set) - shprintf("%s[%lu]", - vp->name, - arrayindex(vp)); - else - shf_puts(vp->name, shl_stdout); - if (thing == '-' && (vp->flag&ISSET)) { - char *s = str_val(vp); - - shf_putc('=', shl_stdout); - /* - * AT&T ksh can't have - * justified integers... - */ - if ((vp->flag & - (INTEGER|LJUST|RJUST)) == - INTEGER) - shf_puts(s, shl_stdout); - else - print_value_quoted(s); - } - shf_putc('\n', shl_stdout); - } - /* - * Only report first 'element' of an array with - * no set elements. - */ - if (!any_set) - break; - } - } + } else if (wp[builtin_opt.optind]) { + for (i = builtin_opt.optind; wp[i]; i++) { + varsearch(e->loc, &vp, wp[i], hash(wp[i])); + c_typeset_vardump(vp, flag, thing, pflag, istset); } - } + } else + c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset); return (0); } +static void +c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing, + bool pflag, bool istset) +{ + struct tbl **blockvars, *vp; + + if (l->next) + c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset); + blockvars = ktsort(&l->vars); + while ((vp = *blockvars++)) + c_typeset_vardump(vp, flag, thing, pflag, istset); + /*XXX doesn’t this leak? */ +} + +static void +c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, bool pflag, + bool istset) +{ + struct tbl *tvp; + int any_set = 0; + char *s; + + if (!vp) + return; + + /* + * See if the parameter is set (for arrays, if any + * element is set). + */ + for (tvp = vp; tvp; tvp = tvp->u.array) + if (tvp->flag & ISSET) { + any_set = 1; + break; + } + + /* + * Check attributes - note that all array elements + * have (should have?) the same attributes, so checking + * the first is sufficient. + * + * Report an unset param only if the user has + * explicitly given it some attribute (like export); + * otherwise, after "echo $FOO", we would report FOO... + */ + if (!any_set && !(vp->flag & USERATTRIB)) + return; + if (flag && (vp->flag & flag) == 0) + return; + if (!(vp->flag & ARRAY)) + /* optimise later conditionals */ + any_set = 0; + do { + /* + * Ignore array elements that aren't set unless there + * are no set elements, in which case the first is + * reported on + */ + if (any_set && !(vp->flag & ISSET)) + continue; + /* no arguments */ + if (!thing && !flag) { + if (any_set == 1) { + shprintf("%s %s %s\n", Tset, "-A", vp->name); + any_set = 2; + } + /* + * AT&T ksh prints things like export, integer, + * leftadj, zerofill, etc., but POSIX says must + * be suitable for re-entry... + */ + shprintf("%s %s", Ttypeset, ""); + if (((vp->flag & (ARRAY | ASSOC)) == ASSOC)) + shprintf("%s ", "-n"); + if ((vp->flag & INTEGER)) + shprintf("%s ", "-i"); + if ((vp->flag & EXPORT)) + shprintf("%s ", "-x"); + if ((vp->flag & RDONLY)) + shprintf("%s ", "-r"); + if ((vp->flag & TRACE)) + 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)) + shprintf("%s ", "-Z"); + if ((vp->flag & LCASEV)) + shprintf("%s ", "-l"); + if ((vp->flag & UCASEV_AL)) + shprintf("%s ", "-u"); + if ((vp->flag & INT_U)) + shprintf("%s ", "-U"); + } else if (pflag) { + shprintf("%s %s", istset ? Ttypeset : + (flag & EXPORT) ? Texport : Treadonly, ""); + } + if (any_set) + shprintf("%s[%lu]", vp->name, arrayindex(vp)); + else + shf_puts(vp->name, shl_stdout); + if ((!thing && !flag && pflag) || + (thing == '-' && (vp->flag & ISSET))) { + s = str_val(vp); + shf_putc('=', shl_stdout); + /* AT&T ksh can't have justified integers... */ + if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER) + shf_puts(s, shl_stdout); + else + print_value_quoted(shl_stdout, s); + } + shf_putc('\n', shl_stdout); + + /* + * Only report first 'element' of an array with + * no set elements. + */ + if (!any_set) + return; + } while ((vp = vp->u.array)); +} + int c_alias(const char **wp) { @@ -1053,7 +1077,7 @@ c_alias(const char **wp) shf_puts(ap->name, shl_stdout); if (prefix != '+') { shf_putc('=', shl_stdout); - print_value_quoted(ap->val.s); + print_value_quoted(shl_stdout, ap->val.s); } shf_putc('\n', shl_stdout); } @@ -1078,7 +1102,7 @@ c_alias(const char **wp) shf_puts(ap->name, shl_stdout); if (prefix != '+') { shf_putc('=', shl_stdout); - print_value_quoted(ap->val.s); + print_value_quoted(shl_stdout, ap->val.s); } shf_putc('\n', shl_stdout); } else { @@ -1259,12 +1283,12 @@ c_fgbg(const char **wp) /* format a single kill item */ static char * -kill_fmt_entry(char *buf, size_t buflen, int i, const void *arg) +kill_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg) { const struct kill_info *ki = (const struct kill_info *)arg; i++; - shf_snprintf(buf, buflen, "%*d %*s %s", + shf_snprintf(buf, buflen, "%*u %*s %s", ki->num_width, i, ki->name_width, sigtraps[i].name, sigtraps[i].mess); @@ -1282,7 +1306,7 @@ c_kill(const char **wp) /* assume old style options if -digits or -UPPERCASE */ if ((p = wp[1]) && *p == '-' && (ksh_isdigit(p[1]) || ksh_isupper(p[1]))) { - if (!(t = gettrap(p + 1, true))) { + if (!(t = gettrap(p + 1, false))) { bi_errorf("bad signal '%s'", p + 1); return (1); } @@ -1349,7 +1373,7 @@ c_kill(const char **wp) mess_cols = w; } - print_columns(shl_stdout, NSIG - 1, + print_columns(shl_stdout, (unsigned int)(NSIG - 1), kill_fmt_entry, (void *)&ki, ki.num_width + 1 + ki.name_width + 1 + mess_octs, ki.num_width + 1 + ki.name_width + 1 + mess_cols, @@ -1369,7 +1393,7 @@ c_kill(const char **wp) rv = 1; } else { if (mksh_kill(n, sig) < 0) { - bi_errorf("%s: %s", p, strerror(errno)); + bi_errorf("%s: %s", p, cstrerror(errno)); rv = 1; } } @@ -1477,6 +1501,7 @@ c_getopts(const char **wp) return (optc < 0 ? 1 : rv); } +#ifndef MKSH_NO_CMDLINE_EDITING int c_bind(const char **wp) { @@ -1535,6 +1560,7 @@ c_bind(const char **wp) return (rv); } +#endif int c_shift(const char **wp) @@ -1714,7 +1740,7 @@ c_dot(const char **wp) return (1); } if ((file = search_path(cp, path, R_OK, &errcode)) == NULL) { - bi_errorf("%s: %s", cp, strerror(errcode)); + bi_errorf("%s: %s", cp, cstrerror(errcode)); return (1); } @@ -1729,9 +1755,9 @@ c_dot(const char **wp) argc = 0; argv = NULL; } - if ((i = include(file, argc, argv, 0)) < 0) { + if ((i = include(file, argc, argv, false)) < 0) { /* should not happen */ - bi_errorf("%s: %s", cp, strerror(errno)); + bi_errorf("%s: %s", cp, cstrerror(errno)); return (1); } return (i); @@ -1759,11 +1785,11 @@ c_wait(const char **wp) return (rv); } +static char REPLY[] = "REPLY"; int c_read(const char **wp) { #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; @@ -1775,7 +1801,7 @@ c_read(const char **wp) const char *ccp; XString xs; ptrdiff_t xsave = 0; - struct termios tios; + mksh_ttyst tios; bool restore_tios = false; #if HAVE_SELECT bool hastimeout = false; @@ -1822,7 +1848,7 @@ c_read(const char **wp) #if HAVE_SELECT case 't': if (parse_usec(builtin_opt.optarg, &tv)) { - bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno), + bi_errorf("%s: %s '%s'", Tsynerr, cstrerror(errno), builtin_opt.optarg); return (2); } @@ -1876,7 +1902,7 @@ c_read(const char **wp) #if HAVE_SELECT if (hastimeout) { - gettimeofday(&tvlim, NULL); + mksh_TIME(tvlim); timeradd(&tvlim, &tv, &tvlim); } #endif @@ -1887,8 +1913,8 @@ c_read(const char **wp) fd_set fdset; FD_ZERO(&fdset); - FD_SET(fd, &fdset); - gettimeofday(&tv, NULL); + FD_SET((unsigned int)fd, &fdset); + mksh_TIME(tv); timersub(&tvlim, &tv, &tv); if (tv.tv_sec < 0) { /* timeout expired globally */ @@ -1904,7 +1930,7 @@ c_read(const char **wp) rv = 1; goto c_read_out; default: - bi_errorf("%s: %s", Tselect, strerror(errno)); + bi_errorf("%s: %s", Tselect, cstrerror(errno)); rv = 2; goto c_read_out; } @@ -2021,7 +2047,7 @@ c_read(const char **wp) vp = global(*wp); if (vp->flag & RDONLY) { c_read_splitro: - bi_errorf("%s: %s", *wp, "is read only"); + bi_errorf("read-only: %s", *wp); c_read_spliterr: rv = 2; afree(cp, ATEMP); @@ -2152,7 +2178,7 @@ c_read(const char **wp) afree(allocd, ATEMP); Xfree(xs, xp); if (restore_tios) - tcsetattr(fd, TCSADRAIN, &tios); + mksh_tcset(fd, &tios); return (rv); #undef is_ifsws } @@ -2198,15 +2224,20 @@ c_eval(const char **wp) * If there are no arguments, or only null arguments, * eval shall return a zero exit status; ... */ - /* exstat = subst_exstat; */ /* AT&T ksh88 */ - exstat = 0; /* SUSv4 */ + /* AT&T ksh88: use subst_exstat */ + /* exstat = subst_exstat; */ + /* SUSv4: OR with a high value never written otherwise */ + exstat |= 0x4000; savef = Flag(FERREXIT); - Flag(FERREXIT) = 0; + Flag(FERREXIT) |= 0x80; rv = shell(s, false); Flag(FERREXIT) = savef; source = saves; afree(s, ATEMP); + if (exstat & 0x4000) + /* detect old exstat, use 0 in that case */ + rv = 0; return (rv); } @@ -2225,7 +2256,7 @@ c_trap(const char **wp) for (p = sigtraps, i = NSIG+1; --i >= 0; p++) if (p->trap != NULL) { shf_puts("trap -- ", shl_stdout); - print_value_quoted(p->trap); + print_value_quoted(shl_stdout, p->trap); shprintf(" %s\n", p->name); } return (0); @@ -2260,7 +2291,7 @@ c_exitreturn(const char **wp) const char *arg; if (ksh_getopt(wp, &builtin_opt, null) == '?') - return (1); + goto c_exitreturn_err; arg = wp[builtin_opt.optind]; if (arg) { @@ -2268,7 +2299,7 @@ c_exitreturn(const char **wp) exstat = 1; warningf(true, "%s: %s", arg, "bad number"); } else - exstat = n; + exstat = n & 0xFF; } else if (trap_exstat != -1) exstat = trap_exstat; if (wp[0][0] == 'r') { @@ -2287,7 +2318,7 @@ c_exitreturn(const char **wp) } if (how == LEXIT && !really_exit && j_stopped_running()) { - really_exit = 1; + really_exit = true; how = LSHELL; } @@ -2295,29 +2326,33 @@ c_exitreturn(const char **wp) quitenv(NULL); unwind(how); /* NOTREACHED */ + + c_exitreturn_err: + return (1); } int c_brkcont(const char **wp) { - int n, quit; + unsigned int quit; + int n; struct env *ep, *last_ep = NULL; const char *arg; if (ksh_getopt(wp, &builtin_opt, null) == '?') - return (1); + goto c_brkcont_err; arg = wp[builtin_opt.optind]; if (!arg) n = 1; else if (!bi_getn(arg, &n)) - return (1); - quit = n; - if (quit <= 0) { + goto c_brkcont_err; + if (n <= 0) { /* AT&T ksh does this for non-interactive shells only - weird */ bi_errorf("%s: %s", arg, "bad value"); - return (1); + goto c_brkcont_err; } + quit = (unsigned int)n; /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */ for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv) @@ -2334,7 +2369,7 @@ c_brkcont(const char **wp) * can. We print a message 'cause it helps in debugging * scripts, but don't generate an error (ie, keep going). */ - if (n == quit) { + if ((unsigned int)n == quit) { warningf(true, "%s: %s %s", wp[0], "can't", wp[0]); return (0); } @@ -2345,12 +2380,15 @@ c_brkcont(const char **wp) */ if (last_ep) last_ep->flags &= ~EF_BRKCONT_PASS; - warningf(true, "%s: can only %s %d level(s)", - wp[0], wp[0], n - quit); + warningf(true, "%s: can only %s %u level(s)", + wp[0], wp[0], (unsigned int)n - quit); } unwind(*wp[0] == 'b' ? LBREAK : LCONTIN); /* NOTREACHED */ + + c_brkcont_err: + return (1); } int @@ -2391,7 +2429,11 @@ c_set(const char **wp) * if there are no command substitutions). * Switched ksh (!posix !sh) to POSIX in mksh R39b. */ +#ifdef MKSH_LEGACY_MODE + return (subst_exstat); +#else return (Flag(FSH) ? subst_exstat : 0); +#endif } int @@ -2434,8 +2476,7 @@ c_unset(const char **wp) afree(cp, ATEMP); if ((vp->flag&RDONLY)) { - warningf(true, "%s: %s", vp->name, - "is read only"); + warningf(true, "read-only: %s", vp->name); rv = 1; } else unset(vp, optc); @@ -2491,7 +2532,7 @@ timex(struct op *t, int f, volatile int *xerrok) struct rusage ru0, ru1, cru0, cru1; struct timeval usrtime, systime, tv0, tv1; - gettimeofday(&tv0, NULL); + mksh_TIME(tv0); getrusage(RUSAGE_SELF, &ru0); getrusage(RUSAGE_CHILDREN, &cru0); if (t->left) { @@ -2508,7 +2549,7 @@ timex(struct op *t, int f, volatile int *xerrok) rv = execute(t->left, f | XTIME, xerrok); if (t->left->type == TCOM) tf |= t->left->str[0]; - gettimeofday(&tv1, NULL); + mksh_TIME(tv1); getrusage(RUSAGE_SELF, &ru1); getrusage(RUSAGE_CHILDREN, &cru1); } else @@ -2597,6 +2638,7 @@ c_exec(const char **wp MKSH_A_UNUSED) for (i = 0; i < NUFILE; i++) { if (e->savefd[i] > 0) close(e->savefd[i]); +#ifndef MKSH_LEGACY_MODE /* * keep all file descriptors > 2 private for ksh, * but not for POSIX or legacy/kludge sh @@ -2604,6 +2646,7 @@ c_exec(const char **wp MKSH_A_UNUSED) if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 && e->savefd[i]) fcntl(i, F_SETFD, FD_CLOEXEC); +#endif } e->savefd = NULL; } @@ -2681,7 +2724,7 @@ c_mknod(const char **wp) goto c_mknod_failed; } else if (mkfifo(argv[0], mode)) { c_mknod_failed: - bi_errorf("%s: %s", argv[0], strerror(errno)); + bi_errorf("%s: %s", argv[0], cstrerror(errno)); c_mknod_err: rv = 1; } @@ -2697,7 +2740,7 @@ c_mknod(const char **wp) #endif /*- - test(1) accepts the following grammar: + test(1) roughly accepts the following grammar: oexpr ::= aexpr | aexpr "-o" oexpr ; aexpr ::= nexpr | nexpr "-a" aexpr ; nexpr ::= primary | "!" nexpr ; @@ -2713,19 +2756,21 @@ c_mknod(const char **wp) binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| "-nt"|"-ot"|"-ef"| - "<"|">" # rules used for [[ .. ]] expressions + "<"|">" # rules used for [[ ... ]] expressions ; - operand ::= + operand ::= */ /* POSIX says > 1 for errors */ -#define T_ERR_EXIT 2 +#define T_ERR_EXIT 2 int c_test(const char **wp) { - int argc, res; + int argc, rv, invert = 0; Test_env te; + Test_op op; + const char *lhs, **swp; te.flags = 0; te.isa = ptest_isa; @@ -2735,6 +2780,8 @@ c_test(const char **wp) for (argc = 0; wp[argc]; argc++) ; + mkssert(argc > 0); + mkssert(wp[0] != NULL); if (strcmp(wp[0], "[") == 0) { if (strcmp(wp[--argc], "]") != 0) { @@ -2747,63 +2794,88 @@ c_test(const char **wp) te.wp_end = wp + argc; /* - * Handle the special cases from POSIX.2, section 4.62.4. - * Implementation of all the rules isn't necessary since - * our parser does the right thing for the omitted steps. + * Attempt to conform to POSIX special cases. This is pretty + * dumb code straight-forward from the 2008 spec, but unless + * the old pdksh code doesn't live from so many assumptions. + * It does, though, inline some calls to '(*te.funcname)()'. */ - if (argc <= 5) { - 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; - } + switch (argc - 1) { + case 0: + return (1); + case 1: + ptest_one: + op = TO_STNZE; + goto ptest_unary; + case 2: + ptest_two: + if (ptest_isa(&te, TM_NOT)) { + ++invert; + goto ptest_one; } - - while (--argc >= 0) { - if ((*te.isa)(&te, TM_END)) - return (!0); - if (argc == 3) { - opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); - if ((op = (*te.isa)(&te, TM_BINOP))) { - opnd2 = (*te.getopnd)(&te, op, 1); - res = (*te.eval)(&te, op, opnd1, - opnd2, 1); - if (te.flags & TEF_ERROR) - return (T_ERR_EXIT); - if (invert & 1) - res = !res; - return (!res); - } - /* back up to opnd1 */ - te.pos.wp--; - } - if (argc == 1) { - opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); - res = (*te.eval)(&te, TO_STNZE, opnd1, - NULL, 1); - if (invert & 1) - res = !res; - return (!res); - } - if ((*te.isa)(&te, TM_NOT)) { - invert++; - } else - break; + if ((op = ptest_isa(&te, TM_UNOP))) { + ptest_unary: + rv = test_eval(&te, op, *te.pos.wp++, NULL, true); + ptest_out: + return ((invert & 1) ? rv : !rv); } - te.pos.wp = owp + 1; - te.wp_end = owpend; + /* let the parser deal with anything else */ + break; + case 3: + ptest_three: + swp = te.pos.wp; + /* use inside knowledge of ptest_getopnd inlined below */ + lhs = *te.pos.wp++; + if ((op = ptest_isa(&te, TM_BINOP))) { + /* test lhs op rhs */ + rv = test_eval(&te, op, lhs, *te.pos.wp++, true); + goto ptest_out; + } + /* back up to lhs */ + te.pos.wp = swp; + if (ptest_isa(&te, TM_NOT)) { + ++invert; + goto ptest_two; + } + if (ptest_isa(&te, TM_OPAREN)) { + swp = te.pos.wp; + /* skip operand, without evaluation */ + te.pos.wp++; + /* check for closing parenthesis */ + op = ptest_isa(&te, TM_CPAREN); + /* back up to operand */ + te.pos.wp = swp; + /* if there was a closing paren, handle it */ + if (op) + goto ptest_one; + /* backing up is done before calling the parser */ + } + /* let the parser deal with it */ + break; + case 4: + if (ptest_isa(&te, TM_NOT)) { + ++invert; + goto ptest_three; + } + if (ptest_isa(&te, TM_OPAREN)) { + swp = te.pos.wp; + /* skip two operands, without evaluation */ + te.pos.wp++; + te.pos.wp++; + /* check for closing parenthesis */ + op = ptest_isa(&te, TM_CPAREN); + /* back up to first operand */ + te.pos.wp = swp; + /* if there was a closing paren, handle it */ + if (op) + goto ptest_two; + /* backing up is done before calling the parser */ + } + /* defer this to the parser */ + break; } + /* "The results are unspecified." */ + te.pos.wp = wp + 1; return (test_parse(&te)); } @@ -2839,6 +2911,34 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, if (!do_eval) return (0); +#ifdef DEBUG + switch (op) { + /* Binary operators */ + case TO_STEQL: + case TO_STNEQ: + case TO_STLT: + case TO_STGT: + case TO_INTEQ: + case TO_INTNE: + case TO_INTGT: + case TO_INTGE: + case TO_INTLT: + case TO_INTLE: + case TO_FILEQ: + case TO_FILNT: + case TO_FILOT: + /* consistency check, but does not happen in practice */ + if (!opnd2) { + te->flags |= TEF_ERROR; + return (1); + } + break; + default: + /* for completeness of switch */ + break; + } +#endif + switch (op) { /* @@ -2903,7 +3003,11 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, /* -h or -L */ case TO_FILSYM: +#ifdef MKSH__NO_SYMLINK + return (0); +#else return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode)); +#endif /* -S */ case TO_FILSOCK: @@ -2955,7 +3059,7 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, /* -s */ case TO_FILGZ: - return (stat(opnd1, &b1) == 0 && b1.st_size > 0L); + return (stat(opnd1, &b1) == 0 && (off_t)b1.st_size > (off_t)0); /* -t */ case TO_FILTT: @@ -2968,11 +3072,11 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, /* -O */ case TO_FILUID: - return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid); + return (stat(opnd1, &b1) == 0 && (uid_t)b1.st_uid == ksheuid); /* -G */ case TO_FILGID: - return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid()); + return (stat(opnd1, &b1) == 0 && (gid_t)b1.st_gid == getegid()); /* * Binary Operators @@ -3182,7 +3286,7 @@ static Test_op ptest_isa(Test_env *te, Test_meta meta) { /* Order important - indexed by Test_meta values */ - static const char *const tokens[] = { + static const char * const tokens[] = { "-o", "-a", "!", "(", ")" }; Test_op rv; @@ -3232,7 +3336,7 @@ ptest_error(Test_env *te, int ofs, const char *msg) struct limits { const char *name; int resource; /* resource to get/set */ - int factor; /* multiply by to get rlim_{cur,max} values */ + unsigned int factor; /* multiply by to get rlim_{cur,max} values */ char option; }; @@ -3362,21 +3466,83 @@ c_ulimit(const char **wp) #endif { NULL, 0, 0, 0 } }; - static char opts[3 + NELEM(limits)]; + static const char opts[] = "a" +#ifdef RLIMIT_SBSIZE + "B" +#endif +#ifdef RLIMIT_TCACHE + "C" +#endif +#ifdef RLIMIT_CORE + "c" +#endif +#ifdef RLIMIT_DATA + "d" +#endif +#ifdef RLIMIT_NICE + "e" +#endif +#ifdef RLIMIT_FSIZE + "f" +#endif + "H" +#ifdef RLIMIT_SIGPENDING + "i" +#endif +#ifdef RLIMIT_LOCKS + "L" +#endif +#ifdef RLIMIT_MEMLOCK + "l" +#endif +#ifdef RLIMIT_AIO_MEM + "M" +#endif +#if defined(ULIMIT_M_IS_RSS) || defined(ULIMIT_M_IS_VMEM) + "m" +#endif +#ifdef RLIMIT_NOFILE + "n" +#endif +#ifdef RLIMIT_AIO_OPS + "O" +#endif +#ifdef RLIMIT_PTHREAD + "P" +#endif +#ifdef RLIMIT_NPROC + "p" +#endif +#ifdef RLIMIT_MSGQUEUE + "q" +#endif +#ifdef RLIMIT_RTPRIO + "r" +#endif + "S" +#ifdef RLIMIT_STACK + "s" +#endif +#ifdef RLIMIT_TIME + "T" +#endif +#ifdef RLIMIT_CPU + "t" +#endif +#ifdef RLIMIT_NOVMON + "V" +#endif +#if defined(ULIMIT_V_IS_VMEM) || defined(ULIMIT_V_IS_AS) + "v" +#endif +#ifdef RLIMIT_SWAP + "w" +#endif + ; int how = SOFT | HARD, optc, what = 'f'; bool all = false; const struct limits *l; - if (!opts[0]) { - /* build options string on first call - yuck */ - char *p = opts; - - *p++ = 'H'; *p++ = 'S'; *p++ = 'a'; - for (l = limits; l->name; l++) - *p++ = l->option; - *p = '\0'; - } - while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) switch (optc) { case 'H': @@ -3389,8 +3555,7 @@ c_ulimit(const char **wp) all = true; break; case '?': - bi_errorf("%s: %s", "usage", - "ulimit [-acdfHLlmnpSsTtvw] [value]"); + bi_errorf("usage: ulimit [-%s] [value]", opts); return (1); default: what = optc; @@ -3428,9 +3593,9 @@ set_ulimit(const struct limits *l, const char *v, int how) if (strcmp(v, "unlimited") == 0) val = (rlim_t)RLIM_INFINITY; else { - mksh_ari_t rval; + mksh_uari_t rval; - if (!evaluate(v, &rval, KSH_RETURN_ERROR, false)) + if (!evaluate(v, (mksh_ari_t *)&rval, KSH_RETURN_ERROR, false)) return (1); /* * Avoid problems caused by typos that evaluate misses due @@ -3459,7 +3624,7 @@ set_ulimit(const struct limits *l, const char *v, int how) if (errno == EPERM) bi_errorf("%s exceeds allowable %s limit", v, l->name); else - bi_errorf("bad %s limit: %s", l->name, strerror(errno)); + bi_errorf("bad %s limit: %s", l->name, cstrerror(errno)); return (1); } @@ -3480,7 +3645,7 @@ print_ulimit(const struct limits *l, int how) if (val == (rlim_t)RLIM_INFINITY) shf_puts("unlimited\n", shl_stdout); else - shprintf("%ld\n", (long)(val / l->factor)); + shprintf("%lu\n", (unsigned long)(val / l->factor)); } #endif @@ -3502,7 +3667,7 @@ c_rename(const char **wp) bi_errorf(Tsynerr); else if ((rv = rename(wp[0], wp[1])) != 0) { rv = errno; - bi_errorf("%s: %s", "failed", strerror(rv)); + bi_errorf("%s: %s", "failed", cstrerror(rv)); } return (rv); @@ -3525,7 +3690,7 @@ c_realpath(const char **wp) bi_errorf(Tsynerr); else if ((buf = do_realpath(wp[0])) == NULL) { rv = errno; - bi_errorf("%s: %s", wp[0], strerror(rv)); + bi_errorf("%s: %s", wp[0], cstrerror(rv)); if ((unsigned int)rv > 255) rv = 255; } else { @@ -3540,17 +3705,12 @@ c_realpath(const char **wp) int c_cat(const char **wp) { - int fd = STDIN_FILENO, rv; + int fd = STDIN_FILENO, rv, eno; ssize_t n, w; const char *fn = ""; 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) { @@ -3565,30 +3725,35 @@ c_cat(const char **wp) wp += builtin_opt.optind; rv = 0; + if ((buf = malloc_osfunc(MKSH_CAT_BUFSIZ)) == NULL) { + bi_errorf(Toomem, (unsigned long)MKSH_CAT_BUFSIZ); + return (1); + } + 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)); + eno = errno; + bi_errorf("%s: %s", fn, cstrerror(eno)); rv = 1; continue; } } while (/* CONSTCOND */ 1) { n = blocking_read(fd, (cp = buf), MKSH_CAT_BUFSIZ); + eno = errno; + /* give the user a chance to ^C out */ + intrcheck(); if (n == -1) { - if (errno == EINTR) { - /* give the user a chance to ^C out */ - intrcheck(); + if (eno == EINTR) { /* interrupted, try again */ continue; } /* an error occured during reading */ - rv = errno; - bi_errorf("%s: %s", fn, strerror(rv)); + bi_errorf("%s: %s", fn, cstrerror(eno)); rv = 1; break; } else if (n == 0) @@ -3601,9 +3766,9 @@ c_cat(const char **wp) /* interrupted, try again */ continue; /* an error occured during writing */ - rv = errno; + eno = errno; bi_errorf("%s: %s", "", - strerror(rv)); + cstrerror(eno)); rv = 1; if (fd != STDIN_FILENO) close(fd); @@ -3638,37 +3803,42 @@ c_sleep(const char **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]); + bi_errorf("%s: %s '%s'", Tsynerr, cstrerror(errno), wp[0]); else { #ifndef MKSH_NOPROSPECTOFWORK - sigset_t omask; + sigset_t omask, bmask; - /* block SIGCHLD from interrupting us, though */ - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + /* block a number of signals from interrupting us, though */ + (void)sigemptyset(&bmask); + (void)sigaddset(&bmask, SIGPIPE); + (void)sigaddset(&bmask, SIGCHLD); +#ifdef SIGWINCH + (void)sigaddset(&bmask, SIGWINCH); #endif - if (select(0, NULL, NULL, NULL, &tv) == 0 || errno == EINTR) +#ifdef SIGINFO + (void)sigaddset(&bmask, SIGINFO); +#endif +#ifdef SIGUSR1 + (void)sigaddset(&bmask, SIGUSR1); +#endif +#ifdef SIGUSR2 + (void)sigaddset(&bmask, SIGUSR2); +#endif + sigprocmask(SIG_BLOCK, &bmask, &omask); +#endif + if (select(1, 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)); + bi_errorf("%s: %s", Tselect, cstrerror(errno)); #ifndef MKSH_NOPROSPECTOFWORK + /* this will re-schedule signal delivery */ 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 4a4a275..3b28f23 100644 --- a/src/histrap.c +++ b/src/histrap.c @@ -2,7 +2,8 @@ /* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -26,61 +27,73 @@ #include #endif -__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. - * IRIX doesn't have this constant. - */ -#ifndef MAP_FILE -#define MAP_FILE 0 -#endif +__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.131 2012/12/28 02:28:35 tg Exp $"); Trap sigtraps[NSIG + 1]; static struct sigaction Sigact_ign; #if HAVE_PERSISTENT_HISTORY -static int hist_count_lines(unsigned char *, int); -static int hist_shrink(unsigned char *, int); -static unsigned char *hist_skip_back(unsigned char *,int *,int); -static void histload(Source *, unsigned char *, int); -static void histinsert(Source *, int, const char *); -static void writehistfile(int, char *); -static int sprinkle(int); +static int histload(Source *, unsigned char *, size_t); +static int writehistline(int, int, const char *); +static void writehistfile(int, const char *); #endif static int hist_execute(char *); -static int hist_replace(char **, const char *, const char *, bool); static char **hist_get(const char *, bool, bool); static char **hist_get_oldest(void); -static void histbackup(void); -static char **current; /* current position in history[] */ -static int hstarted; /* set after hist_init() called */ +static bool hstarted; /* set after hist_init() called */ static Source *hist_source; #if HAVE_PERSISTENT_HISTORY +/*XXX imake style */ +#if defined(__linux) +#define caddr_cast(x) ((void *)(x)) +#else +#define caddr_cast(x) ((caddr_t)(x)) +#endif + +/* several OEs do not have these constants */ +#ifndef MAP_FAILED +#define MAP_FAILED caddr_cast(-1) +#endif + +/* some OEs need the default mapping type specified */ +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + /* current history file: name, fd, size */ static char *hname; -static int histfd; -static size_t hsize; +static int histfd = -1; +static off_t histfsize; #endif static const char Tnot_in_history[] = "not in history"; #define Thistory (Tnot_in_history + 7) +static const char TFCEDIT_dollaru[] = "${FCEDIT:-/bin/ed} $_"; +#define Tspdollaru (TFCEDIT_dollaru + 18) + +/* HISTSIZE default: size of saved history, persistent or standard */ +#ifdef MKSH_SMALL +#define MKSH_DEFHISTSIZE 255 +#else +#define MKSH_DEFHISTSIZE 2047 +#endif +/* maximum considered size of persistent history file */ +#define MKSH_MAXHISTFSIZE ((off_t)1048576 * 96) + int c_fc(const char **wp) { struct shf *shf; struct temp *tf; - const char *p; - char *editor = NULL; bool gflag = false, lflag = false, nflag = false, rflag = false, sflag = false; int optc; - const char *first = NULL, *last = NULL; - char **hfirst, **hlast, **hp; + const char *p, *first = NULL, *last = NULL; + char **hfirst, **hlast, **hp, *editor = NULL; if (!Flag(FTALKING_I)) { bi_errorf("history %ss not available", Tfunction); @@ -101,7 +114,7 @@ c_fc(const char **wp) /* almost certainly not overflowing */ editor = alloc(len + 4, ATEMP); memcpy(editor, p, len); - memcpy(editor + len, " $_", 4); + memcpy(editor + len, Tspdollaru, 4); } break; @@ -149,7 +162,7 @@ c_fc(const char **wp) /* Substitute and execute command */ if (sflag) { - char *pat = NULL, *rep = NULL; + char *pat = NULL, *rep = NULL, *line; if (editor || lflag || nflag || rflag) { bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); @@ -175,7 +188,42 @@ c_fc(const char **wp) hist_get_newest(false); if (!hp) return (1); - return (hist_replace(hp, pat, rep, gflag)); + /* hist_replace */ + if (!pat) + strdupx(line, *hp, ATEMP); + else { + char *s, *s1; + size_t len, pat_len, rep_len; + XString xs; + char *xp; + bool any_subst = false; + + pat_len = strlen(pat); + rep_len = strlen(rep); + Xinit(xs, xp, 128, ATEMP); + for (s = *hp; (s1 = strstr(s, pat)) && + (!any_subst || gflag); s = s1 + pat_len) { + any_subst = true; + len = s1 - s; + XcheckN(xs, xp, len + rep_len); + /*; first part */ + memcpy(xp, s, len); + xp += len; + /* replacement */ + memcpy(xp, rep, rep_len); + xp += rep_len; + } + if (!any_subst) { + bi_errorf("bad substitution"); + return (1); + } + len = strlen(s) + 1; + XcheckN(xs, xp, len); + memcpy(xp, s, len); + xp += len; + line = Xclose(xs, xp); + } + return (hist_execute(line)); } if (editor && (lflag || nflag)) { @@ -249,7 +297,7 @@ c_fc(const char **wp) tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps); if (!(shf = tf->shf)) { bi_errorf("can't %s temporary file %s: %s", - "create", tf->name, strerror(errno)); + "create", tf->tffn, cstrerror(errno)); return (1); } for (hp = rflag ? hlast : hfirst; @@ -257,19 +305,19 @@ c_fc(const char **wp) shf_fprintf(shf, "%s\n", *hp); if (shf_close(shf) == EOF) { bi_errorf("can't %s temporary file %s: %s", - "write", tf->name, strerror(errno)); + "write", tf->tffn, cstrerror(errno)); return (1); } /* Ignore setstr errors here (arbitrary) */ - setstr(local("_", false), tf->name, KSH_RETURN_ERROR); + setstr(local("_", false), tf->tffn, KSH_RETURN_ERROR); /* XXX: source should not get trashed by this.. */ { Source *sold = source; int ret; - ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); + ret = command(editor ? editor : TFCEDIT_dollaru, 0); source = sold; if (ret) return (ret); @@ -279,22 +327,22 @@ c_fc(const char **wp) struct stat statb; XString xs; char *xp; - int n; + ssize_t n; - if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { + if (!(shf = shf_open(tf->tffn, O_RDONLY, 0, 0))) { bi_errorf("can't %s temporary file %s: %s", - "open", tf->name, strerror(errno)); + "open", tf->tffn, cstrerror(errno)); return (1); } - if (stat(tf->name, &statb) < 0) + if (stat(tf->tffn, &statb) < 0) n = 128; - else if (statb.st_size > (1024 * 1048576)) { + else if ((off_t)statb.st_size > MKSH_MAXHISTFSIZE) { bi_errorf("%s %s too large: %lu", Thistory, "file", (unsigned long)statb.st_size); goto errout; } else - n = statb.st_size + 1; + n = (size_t)statb.st_size + 1; Xinit(xs, xp, n, hist_source->areap); while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { xp += n; @@ -303,7 +351,7 @@ c_fc(const char **wp) } if (n < 0) { bi_errorf("can't %s temporary file %s: %s", - "read", tf->name, strerror(shf_errno(shf))); + "read", tf->tffn, cstrerror(shf_errno(shf))); errout: shf_close(shf); return (1); @@ -319,11 +367,18 @@ c_fc(const char **wp) static int hist_execute(char *cmd) { + static int last_line = -1; Source *sold; int ret; char *p, *q; - histbackup(); + /* Back up over last histsave */ + if (histptr >= history && last_line != hist_source->line) { + hist_source->line--; + afree(*histptr, APERM); + histptr--; + last_line = hist_source->line; + } for (p = cmd; p; p = q) { if ((q = strchr(p, '\n'))) { @@ -356,48 +411,6 @@ hist_execute(char *cmd) return (ret); } -static int -hist_replace(char **hp, const char *pat, const char *rep, bool globr) -{ - char *line; - - if (!pat) - strdupx(line, *hp, ATEMP); - else { - char *s, *s1; - size_t pat_len = strlen(pat); - size_t rep_len = strlen(rep); - size_t len; - XString xs; - char *xp; - bool any_subst = false; - - Xinit(xs, xp, 128, ATEMP); - for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || globr); - s = s1 + pat_len) { - any_subst = true; - len = s1 - s; - XcheckN(xs, xp, len + rep_len); - /*; first part */ - memcpy(xp, s, len); - xp += len; - /* replacement */ - memcpy(xp, rep, rep_len); - xp += rep_len; - } - if (!any_subst) { - bi_errorf("bad substitution"); - return (1); - } - len = strlen(s) + 1; - XcheckN(xs, xp, len); - memcpy(xp, s, len); - xp += len; - line = Xclose(xs, xp); - } - return (hist_execute(line)); -} - /* * get pointer to history given pattern * pattern is a number or string @@ -462,21 +475,9 @@ hist_get_oldest(void) return (history); } -/* - * Back up over last histsave - */ -static void -histbackup(void) -{ - static int last_line = -1; - - if (histptr >= history && last_line != hist_source->line) { - hist_source->line--; - afree(*histptr, APERM); - histptr--; - last_line = hist_source->line; - } -} +#if !defined(MKSH_NO_CMDLINE_EDITING) && !MKSH_S_NOVI +/* current position in history[] */ +static char **current; /* * Return the current position. @@ -500,6 +501,7 @@ histnum(int n) return (n); } } +#endif /* * This will become unnecessary if hist_get is modified to allow @@ -527,19 +529,18 @@ findhist(int start, int fwd, const char *str, int anchored) } /* - * set history - * this means reallocating the dataspace + * set history; this means reallocating the dataspace */ void -sethistsize(int n) +sethistsize(mksh_ari_t n) { if (n > 0 && n != histsize) { int cursize = histptr - history; /* save most recent history */ if (n < cursize) { - memmove(history, histptr - n, n * sizeof(char *)); - cursize = n; + memmove(history, histptr - n + 1, n * sizeof(char *)); + cursize = n - 1; } history = aresize2(history, n, sizeof(char *), APERM); @@ -551,15 +552,14 @@ sethistsize(int n) #if HAVE_PERSISTENT_HISTORY /* - * set history file - * This can mean reloading/resetting/starting history file - * maintenance + * set history file; this can mean reloading/resetting/starting + * history file maintenance */ void sethistfile(const char *name) { /* if not started then nothing to do */ - if (hstarted == 0) + if (hstarted == false) return; /* if the name is the same as the name we have */ @@ -567,13 +567,13 @@ sethistfile(const char *name) return; /* - * its a new name - possibly + * it's a new name - possibly */ - if (histfd) { + if (histfd != -1) { /* yes the file is open */ (void)close(histfd); - histfd = 0; - hsize = 0; + histfd = -1; + histfsize = 0; afree(hname, APERM); hname = NULL; /* let's reset the history */ @@ -586,13 +586,13 @@ sethistfile(const char *name) #endif /* - * initialise the history vector + * initialise the history vector */ void init_histvec(void) { if (history == (char **)NULL) { - histsize = HISTORYSIZE; + histsize = MKSH_DEFHISTSIZE; history = alloc2(histsize, sizeof(char *), APERM); histptr = history - 1; } @@ -600,11 +600,7 @@ init_histvec(void) /* - * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to - * a) permit HISTSIZE to control number of lines of history stored - * b) maintain a physical history file - * - * It turns out that there is a lot of ghastly hackery here + * It turns out that there is a lot of ghastly hackery here */ #if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY @@ -614,7 +610,7 @@ histsync(void) { bool changed = false; - if (histfd) { + if (histfd != -1) { int lno = hist_source->line; hist_source->line++; @@ -638,6 +634,7 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) char **hp; char *c, *cp; + mkssert(cmd != NULL); strdupx(c, cmd, APERM); if ((cp = strchr(c, '\n')) != NULL) *cp = '\0'; @@ -653,7 +650,7 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) ++*lnp; #if HAVE_PERSISTENT_HISTORY - if (histfd && dowrite) + if (dowrite && histfd != -1) writehistfile(*lnp, c); #endif @@ -670,395 +667,293 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) } /* - * Write history data to a file nominated by HISTFILE - * if HISTFILE is unset then history still happens, but - * the data is not written to a file - * All copies of ksh looking at the file will maintain the - * same history. This is ksh behaviour. + * Write history data to a file nominated by HISTFILE; + * if HISTFILE is unset then history still happens, but + * the data is not written to a file. All copies of ksh + * looking at the file will maintain the same history. + * This is ksh behaviour. * - * This stuff uses mmap() - * if your system ain't got it - then you'll have to undef HISTORYFILE + * This stuff uses mmap() + * + * This stuff is so totally broken it must eventually be + * redesigned, without mmap, better checks, support for + * larger files, etc. and handle partially corrupted files */ /*- - * Open a history file - * Format is: - * Bytes 1, 2: - * HMAGIC - just to check that we are dealing with - * the correct object - * Then follows a number of stored commands - * Each command is - * + * Open a history file + * Format is: + * Bytes 1, 2: + * HMAGIC - just to check that we are dealing with the correct object + * Then follows a number of stored commands + * Each command is + * */ -#define HMAGIC1 0xab -#define HMAGIC2 0xcd -#define COMMAND 0xff +#define HMAGIC1 0xAB +#define HMAGIC2 0xCD +#define COMMAND 0xFF + +#if HAVE_PERSISTENT_HISTORY +static const unsigned char sprinkle[2] = { HMAGIC1, HMAGIC2 }; +#endif void hist_init(Source *s) { #if HAVE_PERSISTENT_HISTORY unsigned char *base; - int lines, fd, rv = 0; - off_t hfsize; + int lines, fd; + enum { hist_init_first, hist_init_retry, hist_init_restore } hs; #endif if (Flag(FTALKING) == 0) return; - hstarted = 1; - + hstarted = true; hist_source = s; #if HAVE_PERSISTENT_HISTORY if ((hname = str_val(global("HISTFILE"))) == NULL) return; strdupx(hname, hname, APERM); + hs = hist_init_first; retry: /* we have a file and are interactive */ - if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) + if ((fd = open(hname, O_RDWR | O_CREAT | O_APPEND, 0600)) < 0) return; histfd = savefd(fd); if (histfd != fd) close(fd); - (void)flock(histfd, LOCK_EX); + mksh_lockfd(histfd); - hfsize = lseek(histfd, (off_t)0, SEEK_END); - hsize = 1024 * 1048576; - if (hfsize < (off_t)hsize) - hsize = (size_t)hfsize; + histfsize = lseek(histfd, (off_t)0, SEEK_END); + if (histfsize > MKSH_MAXHISTFSIZE || hs == hist_init_restore) { + /* we ignore too large files but still append to them */ + /* we also don't need to re-read after truncation */ + goto hist_init_tail; + } else if (histfsize > 2) { + /* we have some data, check its validity */ + base = (void *)mmap(NULL, (size_t)histfsize, PROT_READ, + MAP_FILE | MAP_PRIVATE, histfd, (off_t)0); + if (base == (unsigned char *)MAP_FAILED) + goto hist_init_fail; + if (base[0] != HMAGIC1 || base[1] != HMAGIC2) { + munmap(caddr_cast(base), (size_t)histfsize); + goto hist_init_fail; + } + /* load _all_ data */ + lines = histload(hist_source, base + 2, (size_t)histfsize - 2); + munmap(caddr_cast(base), (size_t)histfsize); + /* check if the file needs to be truncated */ + if (lines > histsize && histptr >= history) { + /* you're fucked up with the current code, trust me */ + char *nhname, **hp; + struct stat sb; - if (hsize == 0) { - /* add magic */ - if (sprinkle(histfd)) { + /* create temporary file */ + nhname = shf_smprintf("%s.%d", hname, (int)procpid); + if ((fd = open(nhname, O_RDWR | O_CREAT | O_TRUNC | + O_EXCL, 0600)) < 0) { + /* just don't truncate then, meh. */ + goto hist_trunc_dont; + } + if (fstat(histfd, &sb) >= 0 && + chown(nhname, sb.st_uid, sb.st_gid)) { + /* abort the truncation then, meh. */ + goto hist_trunc_abort; + } + /* we definitively want some magic in that file */ + if (write(fd, sprinkle, 2) != 2) + goto hist_trunc_abort; + /* and of course the entries */ + hp = history; + while (hp < histptr) { + if (!writehistline(fd, + s->line - (histptr - hp), *hp)) + goto hist_trunc_abort; + ++hp; + } + /* now unlock, close both, rename, rinse, repeat */ + close(fd); + fd = -1; + hist_finish(); + if (rename(nhname, hname) < 0) { + hist_trunc_abort: + if (fd != -1) + close(fd); + unlink(nhname); + if (fd != -1) + goto hist_trunc_dont; + /* darn! restore histfd and pray */ + } + hs = hist_init_restore; + hist_trunc_dont: + afree(nhname, ATEMP); + if (hs == hist_init_restore) + goto retry; + } + } else if (histfsize != 0) { + /* negative or too small... */ + hist_init_fail: + /* ... or mmap failed or illegal */ + hist_finish(); + /* nuke the bogus file then retry, at most once */ + if (!unlink(hname) && hs != hist_init_retry) { + hs = hist_init_retry; + goto retry; + } + if (hs != hist_init_retry) + bi_errorf("can't %s %s: %s", + "unlink HISTFILE", hname, cstrerror(errno)); + histfsize = 0; + return; + } else { + /* size 0, add magic to the history file */ + if (write(histfd, sprinkle, 2) != 2) { hist_finish(); return; } - } else if (hsize > 0) { - /* - * we have some data - */ - base = (void *)mmap(NULL, hsize, PROT_READ, - MAP_FILE | MAP_PRIVATE, histfd, (off_t)0); - /* - * check on its validity - */ - if (base == (unsigned char *)MAP_FAILED || - *base != HMAGIC1 || base[1] != HMAGIC2) { - if (base != (unsigned char *)MAP_FAILED) - munmap((caddr_t)base, hsize); - hist_finish(); - if (unlink(hname) /* fails */) - goto hiniterr; - goto retry; - } - if (hsize > 2) { - lines = hist_count_lines(base+2, hsize-2); - if (lines > histsize) { - /* we need to make the file smaller */ - if (hist_shrink(base, hsize)) - rv = unlink(hname); - munmap((caddr_t)base, hsize); - hist_finish(); - if (rv) { - hiniterr: - bi_errorf("can't %s %s: %s", - "unlink HISTFILE", hname, - strerror(errno)); - hsize = 0; - return; - } - goto retry; - } - } - histload(hist_source, base+2, hsize-2); - munmap((caddr_t)base, hsize); } - (void)flock(histfd, LOCK_UN); - hfsize = lseek(histfd, (off_t)0, SEEK_END); - hsize = 1024 * 1048576; - if (hfsize < (off_t)hsize) - hsize = hfsize; + histfsize = lseek(histfd, (off_t)0, SEEK_END); + hist_init_tail: + mksh_unlkfd(histfd); #endif } #if HAVE_PERSISTENT_HISTORY -typedef enum state { - shdr, /* expecting a header */ - sline, /* looking for a null byte to end the line */ - sn1, /* bytes 1 to 4 of a line no */ - sn2, sn3, sn4 -} State; - -static int -hist_count_lines(unsigned char *base, int bytes) -{ - State state = shdr; - int lines = 0; - - while (bytes--) { - switch (state) { - case shdr: - if (*base == COMMAND) - state = sn1; - break; - case sn1: - state = sn2; break; - case sn2: - state = sn3; break; - case sn3: - state = sn4; break; - case sn4: - state = sline; break; - case sline: - if (*base == '\0') { - lines++; - state = shdr; - } - } - base++; - } - return (lines); -} - /* - * Shrink the history file to histsize lines + * load the history structure from the stored data */ static int -hist_shrink(unsigned char *oldbase, int oldbytes) +histload(Source *s, unsigned char *base, size_t bytes) { - int fd, rv = 0; - char *nfile = NULL; - struct stat statb; - unsigned char *nbase = oldbase; - int nbytes = oldbytes; + int lno = 0, lines = 0; + unsigned char *cp; - nbase = hist_skip_back(nbase, &nbytes, histsize); - if (nbase == NULL) - return (1); - if (nbase == oldbase) - return (0); + histload_loop: + /* !bytes check as some systems (older FreeBSDs) have buggy memchr */ + if (!bytes || (cp = memchr(base, COMMAND, bytes)) == NULL) + return (lines); + /* advance base pointer past COMMAND byte */ + bytes -= ++cp - base; + base = cp; + /* if there is no full string left, don't bother with the rest */ + if (bytes < 5 || (cp = memchr(base + 4, '\0', bytes - 4)) == NULL) + return (lines); + /* load the stored line number */ + lno = ((base[0] & 0xFF) << 24) | ((base[1] & 0xFF) << 16) | + ((base[2] & 0xFF) << 8) | (base[3] & 0xFF); + /* store away the found line (@base[4]) */ + ++lines; + if (histptr >= history && lno - 1 != s->line) { + /* a replacement? */ + char **hp; - /* - * create temp file - */ - nfile = shf_smprintf("%s.%d", hname, (int)procpid); - if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) - goto errout; - if (fstat(histfd, &statb) >= 0 && - chown(nfile, statb.st_uid, statb.st_gid)) - goto errout; - - if (sprinkle(fd) || write(fd, nbase, nbytes) != nbytes) - goto errout; - close(fd); - fd = -1; - - /* - * rename - */ - if (rename(nfile, hname) < 0) { - errout: - if (fd >= 0) { - close(fd); - if (nfile) - unlink(nfile); + if (lno >= s->line - (histptr - history) && lno <= s->line) { + hp = &histptr[lno - s->line]; + if (*hp) + afree(*hp, APERM); + strdupx(*hp, (char *)(base + 4), APERM); } - rv = 1; + } else { + s->line = lno--; + histsave(&lno, (char *)(base + 4), false, false); } - afree(nfile, ATEMP); - return (rv); + /* advance base pointer past NUL */ + bytes -= ++cp - base; + base = cp; + /* repeat until no more */ + goto histload_loop; } /* - * find a pointer to the data 'no' back from the end of the file - * return the pointer and the number of bytes left - */ -static unsigned char * -hist_skip_back(unsigned char *base, int *bytes, int no) -{ - int lines = 0; - unsigned char *ep; - - for (ep = base + *bytes; --ep > base; ) { - /* - * this doesn't really work: the 4 byte line number that - * is encoded after the COMMAND byte can itself contain - * the COMMAND byte.... - */ - for (; ep > base && *ep != COMMAND; ep--) - ; - if (ep == base) - break; - if (++lines == no) { - *bytes = *bytes - ((char *)ep - (char *)base); - return (ep); - } - } - return (NULL); -} - -/* - * load the history structure from the stored data + * write a command to the end of the history file + * + * This *MAY* seem easy but it's also necessary to check + * that the history file has not changed in size. + * If it has - then some other shell has written to it and + * we should (re)read those commands to update our history */ static void -histload(Source *s, unsigned char *base, int bytes) -{ - State state; - int lno = 0; - unsigned char *line = NULL; - - for (state = shdr; bytes-- > 0; base++) { - switch (state) { - case shdr: - if (*base == COMMAND) - state = sn1; - break; - case sn1: - lno = (((*base)&0xff)<<24); - state = sn2; - break; - case sn2: - lno |= (((*base)&0xff)<<16); - state = sn3; - break; - case sn3: - lno |= (((*base)&0xff)<<8); - state = sn4; - break; - case sn4: - lno |= (*base)&0xff; - line = base+1; - state = sline; - break; - case sline: - if (*base == '\0') { - /* worry about line numbers */ - if (histptr >= history && lno-1 != s->line) { - /* a replacement ? */ - histinsert(s, lno, (char *)line); - } else { - s->line = lno--; - histsave(&lno, (char *)line, false, - false); - } - state = shdr; - } - } - } -} - -/* - * Insert a line into the history at a specified number - */ -static void -histinsert(Source *s, int lno, const char *line) -{ - char **hp; - - if (lno >= s->line - (histptr - history) && lno <= s->line) { - hp = &histptr[lno - s->line]; - if (*hp) - afree(*hp, APERM); - strdupx(*hp, line, APERM); - } -} - -/* - * write a command to the end of the history file - * This *MAY* seem easy but it's also necessary to check - * that the history file has not changed in size. - * If it has - then some other shell has written to it - * and we should read those commands to update our history - */ -static void -writehistfile(int lno, char *cmd) +writehistfile(int lno, const char *cmd) { off_t sizenow; - ssize_t bytes; - unsigned char *base, *news, hdr[5]; + size_t bytes; + unsigned char *base, *news; - (void)flock(histfd, LOCK_EX); + mksh_lockfd(histfd); sizenow = lseek(histfd, (off_t)0, SEEK_END); - if ((sizenow <= (1024 * 1048576)) && ((size_t)sizenow != hsize)) { - /* - * Things have changed - */ - if ((size_t)sizenow > hsize) { - /* someone has added some lines */ - 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, (size_t)sizenow); - goto bad; - } + if (sizenow < histfsize) { + /* the file has shrunk; give up */ + goto bad; + } + if ( + /* ignore changes when the file is too large */ + sizenow <= MKSH_MAXHISTFSIZE + && + /* the size has changed, we need to do read updates */ + sizenow > histfsize + ) { + /* both sizenow and histfsize are <= MKSH_MAXHISTFSIZE */ + bytes = (size_t)(sizenow - histfsize); + 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 + (size_t)histfsize; + if (*news == COMMAND) { hist_source->line--; histload(hist_source, news, bytes); hist_source->line++; lno = hist_source->line; - munmap((caddr_t)base, (size_t)sizenow); - hsize = (size_t)sizenow; - } else { - /* it has shrunk */ - /* but to what? */ - /* we'll give up for now */ + } else + bytes = 0; + munmap(caddr_cast(base), (size_t)sizenow); + if (!bytes) goto bad; - } } - if (cmd) { - /* - * we can write our bit now - */ - hdr[0] = COMMAND; - hdr[1] = (lno>>24)&0xff; - hdr[2] = (lno>>16)&0xff; - hdr[3] = (lno>>8)&0xff; - hdr[4] = lno&0xff; - bytes = strlen(cmd) + 1; - if ((write(histfd, hdr, 5) != 5) || - (write(histfd, cmd, bytes) != bytes)) - goto bad; - 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; + if (cmd && !writehistline(histfd, lno, cmd)) { bad: - hist_finish(); + hist_finish(); + return; + } + histfsize = lseek(histfd, (off_t)0, SEEK_END); + mksh_unlkfd(histfd); +} + +static int +writehistline(int fd, int lno, const char *cmd) +{ + ssize_t n; + unsigned char hdr[5]; + + hdr[0] = COMMAND; + hdr[1] = (lno >> 24) & 0xFF; + hdr[2] = (lno >> 16) & 0xFF; + hdr[3] = (lno >> 8) & 0xFF; + hdr[4] = lno & 0xFF; + n = strlen(cmd) + 1; + return (write(fd, hdr, 5) == 5 && write(fd, cmd, n) == n); } void hist_finish(void) { - (void)flock(histfd, LOCK_UN); - (void)close(histfd); - histfd = 0; -} - -/* - * add magic to the history file - */ -static int -sprinkle(int fd) -{ - static const unsigned char mag[] = { HMAGIC1, HMAGIC2 }; - - return (write(fd, mag, 2) != 2); + if (histfd >= 0) { + mksh_unlkfd(histfd); + (void)close(histfd); + } + histfd = -1; } #endif + #if !HAVE_SYS_SIGNAME static const struct mksh_sigpair { - const char *const name; + const char * const name; int nr; } mksh_sigpairs[] = { #include "signames.inc" @@ -1066,6 +961,12 @@ static const struct mksh_sigpair { }; #endif +#if HAVE_SYS_SIGLIST +#if !HAVE_SYS_SIGLIST_DECL +extern const char * const sys_siglist[]; +#endif +#endif + void inittraps(void) { @@ -1095,8 +996,14 @@ inittraps(void) else { char *s; - if (!strncasecmp(cs, "SIG", 3)) + /* this is not optimal, what about SIGSIG1? */ + if ((cs[0] & 0xDF) == 'S' && + (cs[1] & 0xDF) == 'I' && + (cs[2] & 0xDF) == 'G' && + cs[3] != '\0') { + /* skip leading "SIG" */ cs += 3; + } strdupx(s, cs, APERM); sigtraps[i].name = s; while ((*s = ksh_toupper(*s))) @@ -1164,27 +1071,45 @@ alarm_catcher(int sig MKSH_A_UNUSED) } Trap * -gettrap(const char *name, int igncase) +gettrap(const char *cs, bool igncase) { - int n = NSIG + 1; + int i; Trap *p; - const char *n2; - int (*cmpfunc)(const char *, const char *) = strcmp; + char *as; - if (ksh_isdigit(*name)) { - if (getn(name, &n) && 0 <= n && n < NSIG) - return (&sigtraps[n]); - else - return (NULL); + if (ksh_isdigit(*cs)) { + return ((getn(cs, &i) && 0 <= i && i < NSIG) ? + (&sigtraps[i]) : NULL); } - n2 = strncasecmp(name, "SIG", 3) ? NULL : name + 3; - if (igncase) - cmpfunc = strcasecmp; - for (p = sigtraps; --n >= 0; p++) - if (!cmpfunc(p->name, name) || (n2 && !cmpfunc(p->name, n2))) - return (p); - return (NULL); + /* this breaks SIGSIG1, but we do that above anyway */ + if ((cs[0] & 0xDF) == 'S' && + (cs[1] & 0xDF) == 'I' && + (cs[2] & 0xDF) == 'G' && + cs[3] != '\0') { + /* skip leading "SIG" */ + cs += 3; + } + if (igncase) { + char *s; + + strdupx(as, cs, ATEMP); + cs = s = as; + while ((*s = ksh_toupper(*s))) + ++s; + } else + as = NULL; + + p = sigtraps; + for (i = 0; i <= NSIG; i++) { + if (!strcmp(p->name, cs)) + goto found; + ++p; + } + p = NULL; + found: + afree(as, ATEMP); + return (p); } /* @@ -1194,7 +1119,7 @@ void trapsig(int i) { Trap *p = &sigtraps[i]; - int errno_sv = errno; + int eno = errno; trap = p->set = 1; if (p->flags & TF_DFL_INTR) @@ -1205,7 +1130,7 @@ trapsig(int i) } if (p->shtrap) (*p->shtrap)(i); - errno = errno_sv; + errno = eno; } /* @@ -1306,12 +1231,12 @@ runtrap(Trap *p, bool is_last) /* SIG_DFL */ if (p->flags & TF_FATAL) { /* eg, SIGHUP */ - exstat = 128 + i; + exstat = (int)ksh_min(128U + (unsigned)i, 255U); unwind(LLEAVE); } if (p->flags & TF_DFL_INTR) { /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */ - exstat = 128 + i; + exstat = (int)ksh_min(128U + (unsigned)i, 255U); unwind(LINTR); } goto donetrap; @@ -1326,7 +1251,7 @@ runtrap(Trap *p, bool is_last) p->trap = NULL; } if (trap_exstat == -1) - trap_exstat = exstat; + trap_exstat = exstat & 0xFF; /* * Note: trapstr is fully parsed before anything is executed, thus * no problem with afree(p->trap) in settrap() while still in use. @@ -1457,6 +1382,8 @@ setsig(Trap *p, sig_t f, int flags) if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR) return (1); + memset(&sigact, 0, sizeof(sigact)); + /* * First time setting this signal? If so, get and note the current * setting. @@ -1530,3 +1457,56 @@ setexecsig(Trap *p, int restore) break; } } + +#if HAVE_PERSISTENT_HISTORY || defined(DF) +/* + * File descriptor locking and unlocking functions. + * Could use some error handling, but hey, this is only + * advisory locking anyway, will often not work over NFS, + * and you are SOL if this fails... + */ + +void +mksh_lockfd(int fd) +{ +#if defined(__OpenBSD__) + /* flock is not interrupted by signals */ + (void)flock(fd, LOCK_EX); +#elif HAVE_FLOCK + int rv; + + /* e.g. on Linux */ + do { + rv = flock(fd, LOCK_EX); + } while (rv == 1 && errno == EINTR); +#elif HAVE_LOCK_FCNTL + int rv; + struct flock lks; + + memset(&lks, 0, sizeof(lks)); + lks.l_type = F_WRLCK; + do { + rv = fcntl(fd, F_SETLKW, &lks); + } while (rv == 1 && errno == EINTR); +#endif +} + +/* designed to not define mksh_unlkfd if none triggered */ +#if HAVE_FLOCK +void +mksh_unlkfd(int fd) +{ + (void)flock(fd, LOCK_UN); +} +#elif HAVE_LOCK_FCNTL +void +mksh_unlkfd(int fd) +{ + struct flock lks; + + memset(&lks, 0, sizeof(lks)); + lks.l_type = F_UNLCK; + (void)fcntl(fd, F_SETLKW, &lks); +} +#endif +#endif diff --git a/src/jobs.c b/src/jobs.c index 0b5df4e..4be7356 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -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, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 * Thorsten Glaser * * 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.81 2011/08/27 18:06:46 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.94 2012/12/28 02:28:36 tg Exp $"); #if HAVE_KILLPG #define mksh_killpg killpg @@ -37,13 +37,15 @@ __RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.81 2011/08/27 18:06:46 tg Exp $"); #define PSIGNALLED 2 #define PSTOPPED 3 -typedef struct proc Proc; +typedef struct proc Proc; struct proc { Proc *next; /* next process in pipeline (if any) */ pid_t pid; /* process id */ int state; int status; /* wait status */ - char command[48]; /* process command string */ + /* process command string from vistree */ + char command[64 - (ALLOC_SIZE + sizeof(Proc *) + sizeof(pid_t) + + 2 * sizeof(int))]; }; /* Notify/print flag - j_print() argument */ @@ -87,7 +89,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 ttystat; /* saved tty state for stopped jobs */ + mksh_ttyst ttystat; /* saved tty state for stopped jobs */ pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ #endif }; @@ -105,7 +107,7 @@ struct job { #define JL_AMBIG 2 /* %foo or %?foo is ambiguous */ #define JL_INVALID 3 /* non-pid, non-% job id */ -static const char *const lookup_msgs[] = { +static const char * const lookup_msgs[] = { null, "no such job", "ambiguous", @@ -150,6 +152,9 @@ static void put_job(Job *, int); static void remove_job(Job *, const char *); static int kill_job(Job *, int); +static void tty_init_talking(void); +static void tty_init_state(void); + /* initialise job control */ void j_init(void) @@ -199,13 +204,15 @@ j_init(void) } } - /* j_change() calls tty_init() */ + /* j_change() calls tty_init_talking() and tty_init_state() */ if (Flag(FMONITOR)) j_change(); else #endif - if (Flag(FTALKING)) - tty_init(true, true); + if (Flag(FTALKING)) { + tty_init_talking(); + tty_init_state(); + } } static int @@ -284,12 +291,12 @@ j_change(void) if (Flag(FMONITOR)) { bool use_tty = Flag(FTALKING); - /* Don't call tcgetattr() 'til we own the tty process group */ + /* don't call mksh_tcget until we own the tty process group */ if (use_tty) - tty_init(false, true); + tty_init_talking(); /* no controlling tty, no SIGT* */ - if ((ttypgrp_ok = use_tty && tty_fd >= 0 && tty_devtty)) { + if ((ttypgrp_ok = (use_tty && tty_fd >= 0 && tty_devtty))) { setsig(&sigtraps[SIGTTIN], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); /* wait to be given tty (POSIX.1, B.2, job control) */ @@ -299,7 +306,7 @@ j_change(void) if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { warningf(false, "%s: %s %s: %s", "j_init", "tcgetpgrp", "failed", - strerror(errno)); + cstrerror(errno)); ttypgrp_ok = false; break; } @@ -314,24 +321,24 @@ j_change(void) if (ttypgrp_ok && kshpgrp != kshpid) { if (setpgid(0, kshpid) < 0) { warningf(false, "%s: %s %s: %s", "j_init", - "setpgid", "failed", strerror(errno)); + "setpgid", "failed", cstrerror(errno)); ttypgrp_ok = false; } else { if (tcsetpgrp(tty_fd, kshpid) < 0) { warningf(false, "%s: %s %s: %s", "j_init", "tcsetpgrp", "failed", - strerror(errno)); + cstrerror(errno)); ttypgrp_ok = false; } else restore_ttypgrp = kshpgrp; kshpgrp = kshpid; } } +#ifndef MKSH_DISABLE_TTY_WARNING if (use_tty && !ttypgrp_ok) warningf(false, "%s: %s", "warning", "won't have full job control"); - if (tty_fd >= 0) - tcgetattr(tty_fd, &tty_state); +#endif } else { ttypgrp_ok = false; if (Flag(FTALKING)) @@ -347,9 +354,8 @@ j_change(void) SIG_IGN : SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); } - if (!Flag(FTALKING)) - tty_close(); } + tty_init_state(); } #endif @@ -359,12 +365,12 @@ static void ksh_nice(int ness) { #if defined(__USE_FORTIFY_LEVEL) && (__USE_FORTIFY_LEVEL > 0) - int e; + int eno; 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)); + if (nice(ness) == -1 && (eno = errno) != 0) + warningf(false, "%s: %s", "bgnice", cstrerror(eno)); #else (void)nice(ness); #endif @@ -473,6 +479,7 @@ exchild(struct op *t, int flags, /* job control set up */ if (Flag(FMONITOR) && !(flags&XXCOM)) { bool dotty = false; + if (j->pgrp == 0) { /* First process */ j->pgrp = p->pid; @@ -542,7 +549,6 @@ exchild(struct op *t, int flags, Flag(FMONITOR) = 0; #endif Flag(FTALKING) = 0; - tty_close(); cleartraps(); /* no return */ execute(t, (flags & XERROK) | XEXEC, NULL); @@ -729,7 +735,7 @@ j_kill(const char *cp, int sig) if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */ if (kill_job(j, sig) < 0) { - bi_errorf("%s: %s", cp, strerror(errno)); + bi_errorf("%s: %s", cp, cstrerror(errno)); rv = 1; } } else { @@ -738,7 +744,7 @@ j_kill(const char *cp, int sig) mksh_killpg(j->pgrp, SIGCONT); #endif if (mksh_killpg(j->pgrp, sig) < 0) { - bi_errorf("%s: %s", cp, strerror(errno)); + bi_errorf("%s: %s", cp, cstrerror(errno)); rv = 1; } } @@ -801,20 +807,20 @@ 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->ttystat); + mksh_tcset(tty_fd, &j->ttystat); /* See comment in j_waitj regarding saved_ttypgrp. */ if (ttypgrp_ok && tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ? j->saved_ttypgrp : j->pgrp) < 0) { rv = errno; if (j->flags & JF_SAVEDTTY) - tcsetattr(tty_fd, TCSADRAIN, &tty_state); + mksh_tcset(tty_fd, &tty_state); 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)); + cstrerror(rv)); return (1); } } @@ -825,20 +831,20 @@ j_resume(const char *cp, int bg) } if (j->state == PRUNNING && mksh_killpg(j->pgrp, SIGCONT) < 0) { - int err = errno; + int eno = errno; if (!bg) { j->flags &= ~JF_FG; if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) - tcsetattr(tty_fd, TCSADRAIN, &tty_state); + mksh_tcset(tty_fd, &tty_state); if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0) warningf(true, "%s %s(%d, %ld) %s: %s", "fg: 2nd", "tcsetpgrp", tty_fd, - (long)kshpgrp, "failed", strerror(errno)); + (long)kshpgrp, "failed", cstrerror(errno)); } sigprocmask(SIG_SETMASK, &omask, NULL); bi_errorf("%s %s %s", "can't continue job", - cp, strerror(err)); + cp, cstrerror(eno)); return (1); } if (!bg) { @@ -1060,6 +1066,9 @@ j_waitj(Job *j, const char *where) { int rv; +#ifdef MKSH_NO_SIGSUSPEND + sigset_t omask; +#endif /* * No auto-notify on the job we are waiting on. @@ -1076,7 +1085,14 @@ j_waitj(Job *j, while (j->state == PRUNNING || ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) { #ifndef MKSH_NOPROSPECTOFWORK +#ifdef MKSH_NO_SIGSUSPEND + sigprocmask(SIG_SETMASK, &sm_default, &omask); + pause(); + /* note that handlers may run here so they need to know */ + sigprocmask(SIG_SETMASK, &omask, NULL); +#else sigsuspend(&sm_default); +#endif #else j_sigchld(SIGCHLD); #endif @@ -1115,14 +1131,14 @@ j_waitj(Job *j, if (tcsetpgrp(tty_fd, kshpgrp) < 0) warningf(true, "%s %s(%d, %ld) %s: %s", "j_waitj:", "tcsetpgrp", tty_fd, - (long)kshpgrp, "failed", strerror(errno)); + (long)kshpgrp, "failed", cstrerror(errno)); if (j->state == PSTOPPED) { j->flags |= JF_SAVEDTTY; - tcgetattr(tty_fd, &j->ttystat); + mksh_tcget(tty_fd, &j->ttystat); } } #endif - if (tty_fd >= 0) { + if (tty_hasstate) { /* * Only restore tty settings if job was originally * started in the foreground. Problems can be @@ -1134,9 +1150,9 @@ j_waitj(Job *j, */ if (j->state == PEXITED && j->status == 0 && (j->flags & JF_USETTYMODE)) { - tcgetattr(tty_fd, &tty_state); + mksh_tcget(tty_fd, &tty_state); } else { - tcsetattr(tty_fd, TCSADRAIN, &tty_state); + mksh_tcset(tty_fd, &tty_state); /*- * Don't use tty mode if job is stopped and * later restarted and exits. Consider @@ -1242,6 +1258,12 @@ j_sigchld(int sig MKSH_A_UNUSED) pid_t pid; int status; struct rusage ru0, ru1; +#ifdef MKSH_NO_SIGSUSPEND + sigset_t omask; + + /* this handler can run while SIGCHLD is not blocked, so block it now */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif #ifndef MKSH_NOPROSPECTOFWORK /* @@ -1253,7 +1275,7 @@ j_sigchld(int sig MKSH_A_UNUSED) for (j = job_list; j; j = j->next) if (j->ppid == procpid && !(j->flags & JF_STARTED)) { held_sigchld = 1; - return; + goto j_sigchld_out; } #endif @@ -1270,7 +1292,7 @@ j_sigchld(int sig MKSH_A_UNUSED) * or interrupted (-1) */ if (pid <= 0) - return; + goto j_sigchld_out; getrusage(RUSAGE_CHILDREN, &ru1); @@ -1313,6 +1335,12 @@ j_sigchld(int sig MKSH_A_UNUSED) #else while (/* CONSTCOND */ 0); #endif + + j_sigchld_out: +#ifdef MKSH_NO_SIGSUSPEND + sigprocmask(SIG_SETMASK, &omask, NULL); +#endif + /* nothing */; } /* @@ -1491,6 +1519,8 @@ j_print(Job *j, int how, struct shf *shf) strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess, sizeof(buf)); break; + default: + buf[0] = '\0'; } if (how != JP_SHORT) { @@ -1692,10 +1722,13 @@ remove_job(Job *j, const char *where) Proc *p, *tmp; Job **prev, *curr; + mkssert(j != NULL); prev = &job_list; - curr = *prev; - for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev) - ; + curr = job_list; + while (curr && curr != j) { + prev = &curr->next; + curr = *prev; + } if (curr != j) { internal_warningf("remove_job: job %s (%s)", "not found", where); return; @@ -1732,11 +1765,14 @@ put_job(Job *j, int where) { Job **prev, *curr; + mkssert(j != NULL); /* Remove job from list (if there) */ prev = &job_list; curr = job_list; - for (; curr && curr != j; prev = &curr->next, curr = *prev) - ; + while (curr && curr != j) { + prev = &curr->next; + curr = *prev; + } if (curr == j) *prev = curr->next; @@ -1775,3 +1811,41 @@ kill_job(Job *j, int sig) rval = -1; return (rval); } + +static void +tty_init_talking(void) +{ + switch (tty_init_fd()) { + case 0: + break; + case 1: +#ifndef MKSH_DISABLE_TTY_WARNING + warningf(false, "%s: %s %s: %s", + "No controlling tty", "open", "/dev/tty", + cstrerror(errno)); +#endif + break; + case 2: +#ifndef MKSH_DISABLE_TTY_WARNING + warningf(false, "%s: %s", "can't find tty fd", cstrerror(errno)); +#endif + break; + case 3: + warningf(false, "%s: %s %s: %s", "j_ttyinit", + "dup of tty fd", "failed", cstrerror(errno)); + break; + case 4: + warningf(false, "%s: %s: %s", "j_ttyinit", + "can't set close-on-exec flag", cstrerror(errno)); + break; + } +} + +static void +tty_init_state(void) +{ + if (tty_fd >= 0) { + mksh_tcget(tty_fd, &tty_state); + tty_hasstate = true; + } +} diff --git a/src/lex.c b/src/lex.c index 8a1959a..3f60742 100644 --- a/src/lex.c +++ b/src/lex.c @@ -1,7 +1,8 @@ -/* $OpenBSD: lex.c,v 1.45 2011/03/09 09:30:39 okan Exp $ */ +/* $OpenBSD: lex.c,v 1.46 2013/01/20 14:47:46 stsp Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.156 2011/09/07 15:24:16 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.182 2013/02/19 18:45:20 tg Exp $"); /* * states while lexing word @@ -37,13 +38,12 @@ __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.156 2011/09/07 15:24:16 tg Exp $"); #define SQBRACE 7 /* inside "${}" */ #define SBQUOTE 8 /* inside `` */ #define SASPAREN 9 /* inside $(( )) */ -#define SHEREDELIM 10 /* parsing <<,<<- delimiter */ -#define SHEREDQUOTE 11 /* parsing " in <<,<<- delimiter */ +#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 STBRACEKORN 14 /* parsing ${...[#%]...} !FSH */ +#define STBRACEBOURNE 15 /* parsing ${...[#%]...} FSH */ #define SINVALID 255 /* invalid state */ struct sretrace_info { @@ -90,7 +90,7 @@ typedef struct { static void readhere(struct ioword *); static void ungetsc(int); -static void ungetsc_(int); +static void ungetsc_i(int); static int getsc_uu(void); static void getsc_line(Source *); static int getsc_bn(void); @@ -99,16 +99,13 @@ static void s_put(int); static char *get_brace_var(XString *, char *); 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 Lex_state *push_state_i(State_info *, Lex_state *); +static Lex_state *pop_state_i(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 o_getsc() (*source->str != '\0' && *source->str != '\\' && \ @@ -130,7 +127,7 @@ short subshell_nesting_level = 0; return (cev); \ } -#ifdef MKSH_SMALL +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) static int getsc(void); static int @@ -154,13 +151,13 @@ getsc_r(int c) #define PUSH_STATE(s) do { \ if (++statep == state_info.end) \ - statep = push_state_(&state_info, statep); \ + statep = push_state_i(&state_info, statep); \ state = statep->type = (s); \ } while (/* CONSTCOND */ 0) #define POP_STATE() do { \ if (--statep == state_info.base) \ - statep = pop_state_(&state_info, statep); \ + statep = pop_state_i(&state_info, statep); \ state = statep->type; \ } while (/* CONSTCOND */ 0) @@ -247,7 +244,7 @@ yylex(int cf) if (state == SHEREDELIM) { c = getsc(); if (c == '<') { - state = SHERESTRING; + state = SHEREDELIM; while ((c = getsc()) == ' ' || c == '\t') ; ungetsc(c); @@ -259,8 +256,12 @@ yylex(int cf) /* collect non-special or quoted characters to form word */ while (!((c = getsc()) == 0 || - ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) && - ctype(c, C_LEX1)))) { + ((state == SBASE || state == SHEREDELIM) && ctype(c, C_LEX1)))) { + if (state == SBASE && + subshell_nesting_type == /*{*/ '}' && + c == /*{*/ '}') + /* possibly end ${ :;} */ + break; accept_nonword: Xcheck(ws, wp); switch (state) { @@ -269,8 +270,8 @@ yylex(int cf) statep->nparen++; else if (c == ')') statep->nparen--; - else if (statep->nparen == 0 && - (c == /*{*/ '}' || c == statep->ls_adelim.delimiter)) { + else if (statep->nparen == 0 && (c == /*{*/ '}' || + c == (int)statep->ls_adelim.delimiter)) { *wp++ = ADELIM; *wp++ = c; if (c == /*{*/ '}' || --statep->ls_adelim.num == 0) @@ -347,6 +348,22 @@ yylex(int cf) *wp++ = OQUOTE; PUSH_STATE(SDQUOTE); break; + case '$': + /* + * processing of dollar sign belongs into + * Subst, except for those which can open + * a string: $'…' and $"…" + */ + subst_dollar_ex: + c = getsc(); + switch (c) { + case '"': + goto open_sdquote; + case '\'': + goto open_sequote; + default: + goto SubstS; + } default: goto Subst; } @@ -381,8 +398,8 @@ yylex(int cf) } break; case '$': - subst_dollar: c = getsc(); + SubstS: if (c == '(') /*)*/ { c = getsc(); if (c == '(') /*)*/ { @@ -394,14 +411,26 @@ yylex(int cf) } else { ungetsc(c); subst_command: - sp = yyrecursive(); + c = COMSUB; + subst_command2: + sp = yyrecursive(c); cz = strlen(sp) + 1; XcheckN(ws, wp, cz); - *wp++ = COMSUB; + *wp++ = c; memcpy(wp, sp, cz); wp += cz; } } else if (c == '{') /*}*/ { + c = getsc(); + if (ctype(c, C_IFSWS)) { + /* + * non-subenvironment + * "command" substitution + */ + c = FUNSUB; + goto subst_command2; + } + ungetsc(c); *wp++ = OSUBST; *wp++ = '{'; /*}*/ wp = get_brace_var(&ws, wp); @@ -491,20 +520,9 @@ yylex(int cf) *wp++ = '\0'; *wp++ = CSUBST; *wp++ = 'X'; - } else if (c == '\'' && (state == SBASE)) { - /* XXX which other states are valid? */ - *wp++ = OQUOTE; - ignore_backslash_newline++; - PUSH_STATE(SEQUOTE); - 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; @@ -682,7 +700,7 @@ yylex(int cf) if (c == '"') goto open_sdquote; else if (c == '$') - goto subst_dollar; + goto subst_dollar_ex; else if (c == '`') goto subst_gravis; else if (c != /*{*/ '}') @@ -779,98 +797,60 @@ yylex(int cf) ++statep->nparen; goto Sbase2; - /* <<< delimiter */ - case SHERESTRING: - if (c == '\\') { - c = getsc(); - if (c) { - /* trailing \ is lost */ - *wp++ = QCHAR; - *wp++ = c; - } - } else if (c == '$') { - if ((c2 = getsc()) == '\'') { - PUSH_STATE(SEQUOTE); - statep->ls_bool = false; - goto sherestring_quoted; - } else if (c2 == '"') - goto sherestring_dquoted; - ungetsc(c2); - goto sherestring_regular; - } else if (c == '\'') { - PUSH_STATE(SSQUOTE); - sherestring_quoted: - *wp++ = OQUOTE; - ignore_backslash_newline++; - } else if (c == '"') { - sherestring_dquoted: - state = statep->type = SHEREDQUOTE; - *wp++ = OQUOTE; - /* just don't IFS split; no quoting mode */ - } else { - sherestring_regular: - *wp++ = CHAR; - *wp++ = c; - } - break; - - /* <<,<<- delimiter */ + /* <<, <<-, <<< 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 * $ and `...` are not to be treated specially */ - if (c == '\\') { - c = getsc(); - if (c) { + switch (c) { + case '\\': + if ((c = getsc())) { /* trailing \ is lost */ *wp++ = QCHAR; *wp++ = c; } - } else if (c == '$') { + break; + case '\'': + goto open_ssquote; + case '$': if ((c2 = getsc()) == '\'') { + open_sequote: + *wp++ = OQUOTE; + ignore_backslash_newline++; PUSH_STATE(SEQUOTE); statep->ls_bool = false; - goto sheredelim_quoted; - } else if (c2 == '"') - goto sheredelim_dquoted; + break; + } else if (c2 == '"') { + /* FALLTHROUGH */ + case '"': + state = statep->type = SHEREDQUOTE; + PUSH_SRETRACE(); + break; + } ungetsc(c2); - goto sheredelim_regular; - } else if (c == '\'') { - PUSH_STATE(SSQUOTE); - sheredelim_quoted: - *wp++ = OQUOTE; - ignore_backslash_newline++; - } else if (c == '"') { - sheredelim_dquoted: - state = statep->type = SHEREDQUOTE; - *wp++ = OQUOTE; - } else { - sheredelim_regular: + /* FALLTHROUGH */ + default: *wp++ = CHAR; *wp++ = c; } break; - /* " in <<,<<- delimiter */ + /* " in <<, <<-, <<< delimiter */ case SHEREDQUOTE: - if (c == '"') { - *wp++ = CQUOTE; - state = statep->type = - /* dp[1] == '<' means here string */ - Xstring(ws, wp)[1] == '<' ? - SHERESTRING : SHEREDELIM; - } else { + if (c != '"') + goto Subst; + POP_SRETRACE(); + dp = strnul(sp) - 1; + /* remove the trailing double quote */ + *dp = '\0'; + /* store the quoted string */ + *wp++ = OQUOTE; + XcheckN(ws, wp, (dp - sp)); + dp = sp; + while ((c = *dp++)) { if (c == '\\') { - switch (c = getsc()) { - case 0: - /* trailing \ is lost */ + switch ((c = *dp++)) { case '\\': case '"': case '$': @@ -885,6 +865,9 @@ yylex(int cf) *wp++ = CHAR; *wp++ = c; } + afree(sp, ATEMP); + *wp++ = CQUOTE; + state = statep->type = SHEREDELIM; break; /* in *(...|...) pattern (*+?@!) */ @@ -911,7 +894,7 @@ yylex(int cf) yyerror("no closing quote\n"); /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ - if (state == SHEREDELIM || state == SHERESTRING) + if (state == SHEREDELIM) state = SBASE; dp = Xstring(ws, wp); @@ -1033,17 +1016,17 @@ yylex(int cf) sp = yylval.cp; dp = ident; if ((cf & HEREDELIM) && (sp[1] == '<')) - while (dp < ident+IDENT) { + while ((dp - ident) < IDENT) { if ((c = *sp++) == CHAR) *dp++ = *sp++; else if ((c != OQUOTE) && (c != CQUOTE)) break; } else - while (dp < ident+IDENT && (c = *sp++) == CHAR) + while ((dp - ident) < IDENT && (c = *sp++) == CHAR) *dp++ = *sp++; /* Make sure the ident array stays '\0' padded */ - memset(dp, 0, (ident+IDENT) - dp + 1); + memset(dp, 0, (ident + IDENT) - dp + 1); if (c != EOS) /* word is not unquoted */ *ident = '\0'; @@ -1170,7 +1153,7 @@ readhere(struct ioword *iop) /* end of here document marker, what to do? */ switch (c) { case /*(*/ ')': - if (!subshell_nesting_level) + if (!subshell_nesting_type) /*- * not allowed outside $(...) or (...) * => mismatch @@ -1282,6 +1265,7 @@ getsc_uu(void) break; case SSTRING: + case SSTRINGCMDLINE: break; case SWORDS: @@ -1369,7 +1353,7 @@ getsc_line(Source *s) { char *xp = Xstring(s->xs, xp), *cp; bool interactive = Flag(FTALKING) && s->type == SSTDIN; - int have_tty = interactive && (s->flags & SF_TTY); + bool have_tty = tobool(interactive && (s->flags & SF_TTY)); /* Done here to ensure nothing odd happens when a timeout occurs */ XcheckN(s->xs, xp, LINE); @@ -1382,6 +1366,7 @@ getsc_line(Source *s) } if (interactive) change_winsz(); +#ifndef MKSH_NO_CMDLINE_EDITING if (have_tty && ( #if !MKSH_S_NOVI Flag(FVI) || @@ -1395,7 +1380,9 @@ getsc_line(Source *s) nread = 0; xp[nread] = '\0'; xp += nread; - } else { + } else +#endif + { if (interactive) pprompt(prompt, 0); else @@ -1440,25 +1427,6 @@ getsc_line(Source *s) alarm(0); } cp = Xstring(s->xs, xp); -#ifndef MKSH_SMALL - if (interactive && *cp == '!' && cur_prompt == PS1) { - int linelen; - - linelen = Xlength(s->xs, xp); - XcheckN(s->xs, xp, Zfc_e_dash + /* NUL */ 1); - /* reload after potential realloc */ - cp = Xstring(s->xs, xp); - /* change initial '!' into space */ - *cp = ' '; - /* NUL terminate the current string */ - *xp = '\0'; - /* move the actual string forward */ - memmove(cp + Zfc_e_dash, cp, linelen + /* NUL */ 1); - xp += Zfc_e_dash; - /* prepend it with "fc -e -" */ - memcpy(cp, Tfc_e_dash, Zfc_e_dash); - } -#endif s->start = s->str = cp; strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp)); /* Note: if input is all nulls, this is not eof */ @@ -1514,7 +1482,7 @@ set_prompt(int to, Source *s) ps1 = shf_sclose(shf); saved_atemp = ATEMP; newenv(E_ERRH); - if (sigsetjmp(e->jbuf, 0)) { + if (kshsetjmp(e->jbuf)) { prompt = safe_prompt; /* * Don't print an error - assume it has already @@ -1540,7 +1508,8 @@ set_prompt(int to, Source *s) static int dopprompt(const char *cp, int ntruncate, bool doprint) { - int columns = 0, lines = 0, indelimit = 0; + int columns = 0, lines = 0; + bool indelimit = false; char delimiter = 0; /* @@ -1633,7 +1602,7 @@ get_brace_var(XString *wsp, char *wp) c2 = getsc(); ungetsc(c2); - if (c2 != '}') { + if (c2 != /*{*/ '}') { ungetsc(c); goto out; } @@ -1691,7 +1660,7 @@ arraysub(char **strp) XString ws; char *wp, c; /* we are just past the initial [ */ - int depth = 1; + unsigned int depth = 1; Xinit(ws, wp, 32, ATEMP); @@ -1727,10 +1696,10 @@ ungetsc(int c) rp->xp--; rp = rp->next; } - ungetsc_(c); + ungetsc_i(c); } static void -ungetsc_(int c) +ungetsc_i(int c) { if (source->str > source->start) source->str--; @@ -1768,7 +1737,7 @@ getsc_bn(void) if ((c2 = o_getsc_u()) == '\n') /* ignore the \newline; get the next char... */ continue; - ungetsc_(c2); + ungetsc_i(c2); backslash_skip = 1; } return (c); @@ -1781,25 +1750,25 @@ yyskiputf8bom(void) int c; if ((unsigned char)(c = o_getsc_u()) != 0xEF) { - ungetsc_(c); + ungetsc_i(c); return; } if ((unsigned char)(c = o_getsc_u()) != 0xBB) { - ungetsc_(c); - ungetsc_(0xEF); + ungetsc_i(c); + ungetsc_i(0xEF); return; } if ((unsigned char)(c = o_getsc_u()) != 0xBF) { - ungetsc_(c); - ungetsc_(0xBB); - ungetsc_(0xEF); + ungetsc_i(c); + ungetsc_i(0xBB); + ungetsc_i(0xEF); return; } UTFMODE |= 8; } static Lex_state * -push_state_(State_info *si, Lex_state *old_end) +push_state_i(State_info *si, Lex_state *old_end) { Lex_state *news = alloc2(STATE_BSIZE, sizeof(Lex_state), ATEMP); @@ -1810,7 +1779,7 @@ push_state_(State_info *si, Lex_state *old_end) } static Lex_state * -pop_state_(State_info *si, Lex_state *old_end) +pop_state_i(State_info *si, Lex_state *old_end) { Lex_state *old_base = si->base; diff --git a/src/main.c b/src/main.c index b78965e..61b09e2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,11 @@ -/* $OpenBSD: main.c,v 1.47 2011/09/07 11:33:25 otto Exp $ */ +/* $OpenBSD: main.c,v 1.51 2012/09/10 01:25:30 tedu 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 $ */ +/* $OpenBSD: table.c,v 1.15 2012/02/19 07:52:30 otto Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -33,7 +34,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/main.c,v 1.200 2011/10/07 19:51:28 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.260 2013/02/10 21:42:16 tg Exp $"); extern char **environ; @@ -45,6 +46,8 @@ extern char **environ; #define MKSH_DEFAULT_TMPDIR "/tmp" #endif +static uint8_t isuc(const char *); +static int main_init(int, const char *[], Source **, struct block **); void chvt_reinit(void); static void reclaim(void); static void remove_temps(struct temp *); @@ -56,12 +59,12 @@ static void x_sigwinch(int); static const char initifs[] = "IFS= \t\n"; static const char initsubs[] = - "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}"; + "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0} ${EPOCHREALTIME=}"; static const char *initcoms[] = { Ttypeset, "-r", initvsn, NULL, Ttypeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL, - Ttypeset, "-i10", "SECONDS", "TMOUT", NULL, + Ttypeset, "-i10", "COLUMNS", "LINES", "SECONDS", "TMOUT", NULL, Talias, "integer=typeset -i", Tlocal_typeset, @@ -94,7 +97,7 @@ static const char *restr_com[] = { Ttypeset, "-r", "PATH", "ENV", "SHELL", NULL }; -static int initio_done; +static bool initio_done; /* top-level parsing and execution environment */ static struct env env; @@ -107,9 +110,10 @@ rndsetup(void) struct { ALLOC_ITEM alloc_INT; void *dataptr, *stkptr, *mallocptr; +#if defined(__GLIBC__) && (__GLIBC__ >= 2) sigjmp_buf jbuf; +#endif struct timeval tv; - struct timezone tz; } *bufptr; char *cp; @@ -126,10 +130,12 @@ rndsetup(void) bufptr->stkptr = &bufptr; /* randomised malloc in BSD (and possibly others) */ bufptr->mallocptr = bufptr; +#if defined(__GLIBC__) && (__GLIBC__ >= 2) /* glibc pointer guard */ sigsetjmp(bufptr->jbuf, 1); - /* introduce variation */ - gettimeofday(&bufptr->tv, &bufptr->tz); +#endif + /* introduce variation (and yes, second arg MBZ for portability) */ + mksh_TIME(bufptr->tv); NZATInit(h); /* variation through pid, ppid, and the works */ @@ -155,8 +161,31 @@ static const char *empty_argv[] = { "mksh", NULL }; -int -main(int argc, const char *argv[]) +static uint8_t +isuc(const char *cx) { + char *cp, *x; + uint8_t rv = 0; + + if (!cx || !*cx) + return (0); + + /* uppercase a string duplicate */ + strdupx(x, cx, ATEMP); + cp = x; + while ((*cp = ksh_toupper(*cp))) + ++cp; + + /* check for UTF-8 */ + if (strstr(x, "UTF-8") || strstr(x, "UTF8")) + rv = 1; + + /* free copy and out */ + afree(x, ATEMP); + return (rv); +} + +static int +main_init(int argc, const char *argv[], Source **sp, struct block **lp) { int argi, i; Source *s = NULL; @@ -209,8 +238,8 @@ main(int argc, const char *argv[]) /* define built-in commands and see if we were called as one */ ktinit(APERM, &builtins, - /* currently 50 builtins -> 80% of 64 (2^6) */ - 6); + /* currently up to 50 builtins: 75% of 128 = 2^7 */ + 7); for (i = 0; mkshbuiltins[i].name != NULL; i++) if (!strcmp(ccp, builtin(mkshbuiltins[i].name, mkshbuiltins[i].func))) @@ -225,7 +254,7 @@ main(int argc, const char *argv[]) #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); + change_flag(FSH, OF_FIRSTTIME, true); #endif } @@ -249,6 +278,9 @@ main(int argc, const char *argv[]) init_histvec(); + /* initialise tty size before importing environment */ + change_winsz(); + #ifdef _PATH_DEFPATH def_path = _PATH_DEFPATH; #else @@ -291,14 +323,16 @@ main(int argc, const char *argv[]) */ Flag(FBRACEEXPAND) = 1; +#ifndef MKSH_NO_CMDLINE_EDITING /* * Set edit mode to emacs by default, may be overridden * by the environment or the user. Also, we want tab completion * on in vi by default. */ - change_flag(FEMACS, OF_SPECIAL, 1); + change_flag(FEMACS, OF_SPECIAL, true); #if !MKSH_S_NOVI Flag(FVITABCOMPLETE) = 1; +#endif #endif /* import environment */ @@ -330,9 +364,7 @@ main(int argc, const char *argv[]) while (*wp != NULL) wp++; } - setint_n(global("COLUMNS"), 0); - setint_n(global("LINES"), 0); - setint_n(global("OPTIND"), 1); + setint_n(global("OPTIND"), 1, 10); kshuid = getuid(); kshgid = getgid(); @@ -345,21 +377,23 @@ main(int argc, const char *argv[]) (!ksheuid && !strchr(str_val(vp), '#'))) /* setstr can't fail here */ setstr(vp, safe_prompt, KSH_RETURN_ERROR); - setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp); + setint_n((vp = global("BASHPID")), 0, 10); vp->flag |= INT_U; - setint_n((vp = global("PPID")), (mksh_uari_t)kshppid); + setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp, 10); vp->flag |= INT_U; - setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid); + setint_n((vp = global("PPID")), (mksh_uari_t)kshppid, 10); vp->flag |= INT_U; - setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid); + setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid, 10); vp->flag |= INT_U; - setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid); + setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid, 10); vp->flag |= INT_U; - setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid); + setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid, 10); vp->flag |= INT_U; - setint_n((vp = global("RANDOM")), rndsetup()); + setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid, 10); vp->flag |= INT_U; - setint_n((vp_pipest = global("PIPESTATUS")), 0); + setint_n((vp = global("RANDOM")), rndsetup(), 10); + vp->flag |= INT_U; + setint_n((vp_pipest = global("PIPESTATUS")), 0, 10); /* Set this before parsing arguments */ Flag(FPRIVILEGED) = kshuid != ksheuid || kshgid != kshegid; @@ -423,9 +457,19 @@ main(int argc, const char *argv[]) /* auto-detect from environment variables, always */ utf_flag = 3; } else if (Flag(FCOMMAND)) { - s = pushs(SSTRING, ATEMP); + s = pushs(SSTRINGCMDLINE, ATEMP); if (!(s->start = s->str = argv[argi++])) errorf("%s %s", "-c", "requires an argument"); +#if !defined(MKSH_SMALL) + while (*s->str) { + if (*s->str != ' ' && ctype(*s->str, C_QUOTE)) + break; + s->str++; + } + if (!*s->str) + s->flags |= SF_MAYEXEC; + s->str = s->start; +#endif #ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */ if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--")) @@ -440,7 +484,7 @@ main(int argc, const char *argv[]) SHF_MAPHI | SHF_CLEXEC); if (s->u.shf == NULL) { shl_stdout_ok = false; - warningf(true, "%s: %s", s->file, strerror(errno)); + warningf(true, "%s: %s", s->file, cstrerror(errno)); /* mandated by SUSv4 */ exstat = 127; unwind(LERROR); @@ -468,7 +512,7 @@ main(int argc, const char *argv[]) /* initialise job control */ j_init(); - /* Do this after j_init(), as tty_fd is not initialised until then */ + /* do this after j_init() which calls tty_init_state() */ if (Flag(FTALKING)) { if (utf_flag == 2) { #ifndef MKSH_ASSUME_UTF8 @@ -481,7 +525,9 @@ main(int argc, const char *argv[]) utf_flag = 0; #endif } +#ifndef MKSH_NO_CMDLINE_EDITING x_init(); +#endif } #ifdef SIGWINCH @@ -497,14 +543,20 @@ main(int argc, const char *argv[]) l->argv[0] = ccp; } else { l->argc = argc - argi; - l->argv = &argv[argi - 1]; + /* + * allocate a new array because otherwise, when we modify + * it in-place, ps(1) output changes; the meaning of argc + * here is slightly different as it excludes kshname, and + * we add a trailing NULL sentinel as well + */ + l->argv = alloc2(l->argc + 2, sizeof(void *), APERM); l->argv[0] = kshname; + memcpy(&l->argv[1], &argv[argi], l->argc * sizeof(void *)); + l->argv[l->argc + 1] = NULL; 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) { @@ -546,7 +598,6 @@ main(int argc, const char *argv[]) UTFMODE = utf_flag; break; } -#undef isuc /* Disable during .profile/ENV reading */ restricted = Flag(FRESTRICTED); @@ -562,13 +613,13 @@ main(int argc, const char *argv[]) warningf(false, "can't determine current directory"); if (Flag(FLOGIN)) { - include(MKSH_SYSTEM_PROFILE, 0, NULL, 1); + include(MKSH_SYSTEM_PROFILE, 0, NULL, true); if (!Flag(FPRIVILEGED)) include(substitute("$HOME/.profile", 0), 0, - NULL, 1); + NULL, true); } if (Flag(FPRIVILEGED)) - include(MKSH_SUID_PROFILE, 0, NULL, 1); + include(MKSH_SUID_PROFILE, 0, NULL, true); else if (Flag(FTALKING)) { char *env_file; @@ -576,7 +627,7 @@ main(int argc, const char *argv[]) env_file = substitute(substitute("${ENV:-" MKSHRC_PATH "}", 0), DOTILDE); if (*env_file != '\0') - include(env_file, 0, NULL, 1); + include(env_file, 0, NULL, true); } if (restricted) { @@ -586,7 +637,7 @@ main(int argc, const char *argv[]) } Flag(FERREXIT) = errexit; - if (Flag(FTALKING)) + if (Flag(FTALKING) && s) hist_init(s); else /* set after ENV */ @@ -594,17 +645,33 @@ main(int argc, const char *argv[]) alarm_init(); - if (Flag(FAS_BUILTIN)) - return (shcomexec(l->argv)); - - /* doesn't return */ - shell(s, true); - /* NOTREACHED */ + *sp = s; + *lp = l; return (0); } +/* this indirection barrier reduces stack usage during normal operation */ + int -include(const char *name, int argc, const char **argv, int intr_ok) +main(int argc, const char *argv[]) +{ + int rv; + Source *s; + struct block *l; + + if ((rv = main_init(argc, argv, &s, &l)) == 0) { + if (Flag(FAS_BUILTIN)) { + rv = shcomexec(l->argv); + } else { + shell(s, true); + /* NOTREACHED */ + } + } + return (rv); +} + +int +include(const char *name, int argc, const char **argv, bool intr_ok) { Source *volatile s = NULL; struct shf *shf; @@ -624,8 +691,7 @@ include(const char *name, int argc, const char **argv, int intr_ok) old_argc = 0; } newenv(E_INCL); - i = sigsetjmp(e->jbuf, 0); - if (i) { + if ((i = kshsetjmp(e->jbuf))) { quitenv(s ? s->u.shf : NULL); if (old_argv) { e->loc->argv = old_argv; @@ -641,7 +707,7 @@ include(const char *name, int argc, const char **argv, int intr_ok) * 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) + if (intr_ok && ((exstat & 0xFF) - 128) != SIGTERM) return (1); /* FALLTHROUGH */ case LEXIT: @@ -687,62 +753,63 @@ command(const char *comm, int line) * run the commands from the input source, returning status. */ int -shell(Source * volatile s, volatile int toplevel) +shell(Source * volatile s, volatile bool toplevel) { struct op *t; - volatile int wastty = s->flags & SF_TTY; - volatile int attempts = 13; - volatile int interactive = Flag(FTALKING) && toplevel; + volatile bool wastty = tobool(s->flags & SF_TTY); + volatile uint8_t attempts = 13; + volatile bool interactive = Flag(FTALKING) && toplevel; volatile bool sfirst = true; Source *volatile old_source = source; int i; 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 LERROR: - case LSHELL: - if (interactive) { - if (i == LINTR) - shellf("\n"); - /* - * Reset any eof that was read as part of a - * multiline command. - */ - if (Flag(FIGNOREEOF) && s->type == SEOF && - wastty) - s->type = SSTDIN; - /* - * Used by exit command to get back to - * top level shell. Kind of strange since - * interactive is set if we are reading from - * a tty, but to have stopped jobs, one only - * needs FMONITOR set (not FTALKING/SF_TTY)... - */ - /* toss any input we have so far */ - s->start = s->str = null; - break; - } - /* FALLTHROUGH */ - case LEXIT: - case LLEAVE: - case LRETURN: - source = old_source; - quitenv(NULL); - /* keep on going */ - unwind(i); - /* NOTREACHED */ - default: - source = old_source; - quitenv(NULL); - internal_errorf("%s %d", "shell", i); - /* NOTREACHED */ + really_exit = false; + switch ((i = kshsetjmp(e->jbuf))) { + case 0: + break; + 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 + * multiline command. + */ + if (Flag(FIGNOREEOF) && s->type == SEOF && wastty) + s->type = SSTDIN; + /* + * Used by exit command to get back to + * top level shell. Kind of strange since + * interactive is set if we are reading from + * a tty, but to have stopped jobs, one only + * needs FMONITOR set (not FTALKING/SF_TTY)... + */ + /* toss any input we have so far */ + yyrecursive_pop(true); + s->start = s->str = null; + retrace_info = NULL; + herep = heres; + break; } + /* FALLTHROUGH */ + case LEXIT: + case LLEAVE: + case LRETURN: + source = old_source; + quitenv(NULL); + /* keep on going */ + unwind(i); + /* NOTREACHED */ + default: + source = old_source; + quitenv(NULL); + internal_errorf("%s %d", "shell", i); + /* NOTREACHED */ } while (/* CONSTCOND */ 1) { if (trap) @@ -760,13 +827,15 @@ shell(Source * volatile s, volatile int toplevel) } t = compile(s, sfirst); sfirst = false; - if (t != NULL && t->type == TEOF) { + if (!t) + goto source_no_tree; + if (t->type == TEOF) { if (wastty && Flag(FIGNOREEOF) && --attempts > 0) { shellf("Use 'exit' to leave mksh\n"); s->type = SSTDIN; } else if (wastty && !really_exit && j_stopped_running()) { - really_exit = 1; + really_exit = true; s->type = SSTDIN; } else { /* @@ -780,36 +849,59 @@ shell(Source * volatile s, volatile int toplevel) break; } } - if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) - exstat = execute(t, 0, NULL); +#if !defined(MKSH_SMALL) + else if ((s->flags & SF_MAYEXEC) && t->type == TCOM) + t->u.evalflags |= DOTCOMEXEC; +#endif + if (!Flag(FNOEXEC) || (s->flags & SF_TTY)) + exstat = execute(t, 0, NULL) & 0xFF; - if (t != NULL && t->type != TEOF && interactive && really_exit) - really_exit = 0; + if (t->type != TEOF && interactive && really_exit) + really_exit = false; + source_no_tree: reclaim(); } quitenv(NULL); source = old_source; - return (exstat); + return (exstat & 0xFF); } /* return to closest error handler or shell(), exit if none found */ +/* note: i MUST NOT be 0 */ void unwind(int i) { + /* + * This is a kludge. We need to restore everything that was + * changed in the new environment, see cid 1005090337C7A669439 + * and 10050903386452ACBF1, but fail to even save things most of + * the time. funcs.c:c_eval() changes FERREXIT temporarily to 0, + * which needs to be restored thus (related to Debian #696823). + * We did not save the shell flags, so we use a special or'd + * value here... this is mostly to clean up behind *other* + * callers of unwind(LERROR) here; exec.c has the regular case. + */ + if (Flag(FERREXIT) & 0x80) { + /* GNU bash does not run this trapsig */ + trapsig(ksh_SIGERR); + Flag(FERREXIT) &= ~0x80; + } + /* 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[ksh_SIGEXIT].trap)) { + if (i == LEXIT || + ((i == LERROR || i == LINTR) && 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)) { + } else if (Flag(FERREXIT) == 1 && (i == LERROR || i == LINTR)) { ++trap_nested; runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1); --trap_nested; i = LLEAVE; } + while (/* CONSTCOND */ 1) { switch (e->type) { case E_PARSE: @@ -817,7 +909,7 @@ unwind(int i) case E_INCL: case E_LOOP: case E_ERRH: - siglongjmp(e->jbuf, i); + kshlongjmp(e->jbuf, i); /* NOTREACHED */ case E_NONE: if (i == LINTR) @@ -848,6 +940,7 @@ newenv(int type) ep->loc = e->loc; ep->savefd = NULL; ep->temps = NULL; + ep->yyrecursive_statep = NULL; ep->type = type; ep->flags = 0; /* jump buffer is invalid because flags == 0 */ @@ -861,7 +954,8 @@ quitenv(struct shf *shf) char *cp; int fd; - if (ep->oenv && ep->oenv->loc != ep->loc) + yyrecursive_pop(true); + while (ep->oenv && ep->oenv->loc != ep->loc) popblock(); if (ep->savefd != NULL) { for (fd = 0; fd < NUFILE; fd++) @@ -877,6 +971,10 @@ quitenv(struct shf *shf) * Either main shell is exiting or cleanup_parents_env() was called. */ if (ep->oenv == NULL) { +#ifdef DEBUG_LEAKS + int i; +#endif + if (ep->type == E_NONE) { /* Main shell exiting? */ #if HAVE_PERSISTENT_HISTORY @@ -885,7 +983,7 @@ quitenv(struct shf *shf) #endif j_exit(); if (ep->flags & EF_FAKE_SIGDIE) { - int sig = exstat - 128; + int sig = (exstat & 0xFF) - 128; /* * ham up our death a bit (AT&T ksh @@ -904,7 +1002,20 @@ quitenv(struct shf *shf) if (shf) shf_close(shf); reclaim(); - exit(exstat); +#ifdef DEBUG_LEAKS +#ifndef MKSH_NO_CMDLINE_EDITING + x_done(); +#endif + afreeall(APERM); + for (fd = 3; fd < NUFILE; fd++) + if ((i = fcntl(fd, F_GETFD, 0)) != -1 && + (i & FD_CLOEXEC)) + close(fd); + close(2); + close(1); + close(0); +#endif + exit(exstat & 0xFF); } if (shf) shf_close(shf); @@ -941,8 +1052,14 @@ cleanup_parents_env(void) afree(ep->savefd, &ep->area); ep->savefd = NULL; } +#ifdef DEBUG_LEAKS + if (ep->type != E_NONE) + ep->type = E_GONE; +#endif } +#ifndef DEBUG_LEAKS e->oenv = NULL; +#endif } /* Called just before an execve cleanup stuff temporary files */ @@ -959,6 +1076,13 @@ cleanup_proc_env(void) static void reclaim(void) { + struct block *l; + + while ((l = e->loc) && (!e->oenv || e->oenv->loc != l)) { + e->loc = l->next; + afreeall(&l->area); + } + remove_temps(e->temps); e->temps = NULL; afreeall(&e->area); @@ -969,75 +1093,80 @@ remove_temps(struct temp *tp) { for (; tp != NULL; tp = tp->next) if (tp->pid == procpid) - unlink(tp->name); + unlink(tp->tffn); } /* - * Initialise tty_fd. Used for saving/reseting tty modes upon - * foreground job completion and for setting up tty process group. + * Initialise tty_fd. Used for tracking the size of the terminal, + * saving/resetting tty modes upon forground job completion, and + * for setting up the tty process group. Return values: + * 0 = got controlling tty + * 1 = got terminal but no controlling tty + * 2 = cannot find a terminal + * 3 = cannot dup fd + * 4 = cannot make fd close-on-exec + * An existing tty_fd is cached if no "better" one could be found, + * i.e. if tty_devtty was already set or the new would not set it. */ -void -tty_init(bool init_ttystate, bool need_tty) +int +tty_init_fd(void) { - bool do_close = true; - int tfd; + int fd, rv, eno = 0; + bool do_close = false, is_devtty = true; - if (tty_fd >= 0) { - close(tty_fd); - tty_fd = -1; + if (tty_devtty) { + /* already got a tty which is /dev/tty */ + return (0); } - tty_devtty = true; #ifdef _UWIN /*XXX imake style */ if (isatty(3)) { /* fd 3 on UWIN _is_ /dev/tty (or our controlling tty) */ - tfd = 3; - do_close = false; - } else + fd = 3; + goto got_fd; + } #endif - if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { - tty_devtty = false; - if (need_tty) - warningf(false, "%s: %s %s: %s", - "No controlling tty", "open", "/dev/tty", - strerror(errno)); + if ((fd = open("/dev/tty", O_RDWR, 0)) >= 0) { + do_close = true; + goto got_fd; } - if (tfd < 0) { - do_close = false; - if (isatty(0)) - tfd = 0; - else if (isatty(2)) - tfd = 2; - else { - if (need_tty) - warningf(false, "can't find tty fd"); - return; - } - } - if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) { - if (need_tty) - 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, "%s: %s: %s", "j_ttyinit", - "can't set close-on-exec flag", strerror(errno)); - close(tty_fd); - tty_fd = -1; - } else if (init_ttystate) - tcgetattr(tty_fd, &tty_state); - if (do_close) - close(tfd); -} + eno = errno; -void -tty_close(void) -{ if (tty_fd >= 0) { - close(tty_fd); - tty_fd = -1; + /* already got a non-devtty one */ + rv = 1; + goto out; } + is_devtty = false; + + if (isatty((fd = 0)) || isatty((fd = 2))) + goto got_fd; + /* cannot find one */ + rv = 2; + /* assert: do_close == false */ + goto out; + + got_fd: + if ((rv = fcntl(fd, F_DUPFD, FDBASE)) < 0) { + eno = errno; + rv = 3; + goto out; + } + if (fcntl(rv, F_SETFD, FD_CLOEXEC) < 0) { + eno = errno; + close(rv); + rv = 4; + goto out; + } + tty_fd = rv; + tty_devtty = is_devtty; + rv = eno = 0; + out: + if (do_close) + close(fd); + errno = eno; + return (rv); } /* A shell error occurred (eg, syntax error, etc.) */ @@ -1053,7 +1182,7 @@ static void vwarningf(unsigned int, const char *, va_list) static void vwarningf(unsigned int flags, const char *fmt, va_list ap) { - if (*fmt != 1) { + if (fmt) { if (flags & VWARNINGF_INTERNAL) shf_fprintf(shl_out, "internal error: "); if (flags & VWARNINGF_ERRORPREFIX) @@ -1217,17 +1346,47 @@ can_seek(int fd) SHF_UNBUF : 0); } -struct shf shf_iob[3]; +#ifdef DF +int shl_dbg_fd; +#define NSHF_IOB 4 +#else +#define NSHF_IOB 3 +#endif +struct shf shf_iob[NSHF_IOB]; void initio(void) { +#ifdef DF + const char *lfp; +#endif + /* force buffer allocation */ shf_fdopen(1, SHF_WR, shl_stdout); shf_fdopen(2, SHF_WR, shl_out); - /* force buffer allocation */ shf_fdopen(2, SHF_WR, shl_spare); - initio_done = 1; +#ifdef DF + if ((lfp = getenv("SDMKSH_PATH")) == NULL) { + if ((lfp = getenv("HOME")) == NULL || *lfp != '/') + errorf("cannot get home directory"); + lfp = shf_smprintf("%s/mksh-dbg.txt", lfp); + } + + if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0) + errorf("cannot open debug output file %s", lfp); + if (shl_dbg_fd < FDBASE) { + int nfd; + + nfd = fcntl(shl_dbg_fd, F_DUPFD, FDBASE); + close(shl_dbg_fd); + if ((shl_dbg_fd = nfd) == -1) + errorf("cannot dup debug output file"); + } + fcntl(shl_dbg_fd, F_SETFD, FD_CLOEXEC); + shf_fdopen(shl_dbg_fd, SHF_WR, shl_dbg); + DF("=== open ==="); +#endif + initio_done = true; } /* A dup2() with error checking */ @@ -1270,7 +1429,7 @@ void restfd(int fd, int ofd) { if (fd == 2) - shf_flush(&shf_iob[fd]); + shf_flush(&shf_iob[/* fd */ 2]); if (ofd < 0) /* original fd closed */ close(fd); @@ -1433,42 +1592,71 @@ coproc_cleanup(int reuse) struct temp * maketemp(Area *ap, Temp_type type, struct temp **tlist) { - struct temp *tp; + char *cp; size_t len; - int fd; - char *pathname; + int i; + struct temp *tp; const char *dir; + struct stat sb; dir = tmpdir ? tmpdir : MKSH_DEFAULT_TMPDIR; -#if HAVE_MKSTEMP - len = strlen(dir) + 6 + 10 + 1; -#else - 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 - if (pathname == NULL) - tp->name[0] = '\0'; - else { - memcpy(tp->name, pathname, len); - free_ostempnam(pathname); - } -#endif - pathname = tp->name; - tp->shf = NULL; - tp->type = type; -#if HAVE_MKSTEMP - 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) -#endif - tp->shf = shf_fdopen(fd, SHF_WR, NULL); - tp->pid = procpid; + /* add "/shXXXXXX.tmp" plus NUL */ + len = strlen(dir); + checkoktoadd(len, offsetof(struct temp, tffn[0]) + 14); + tp = alloc(offsetof(struct temp, tffn[0]) + 14 + len, ap); + tp->shf = NULL; + tp->pid = procpid; + tp->type = type; + + if (stat(dir, &sb) || !S_ISDIR(sb.st_mode)) { + tp->tffn[0] = '\0'; + goto maketemp_out; + } + + cp = (void *)tp; + cp += offsetof(struct temp, tffn[0]); + memcpy(cp, dir, len); + cp += len; + memcpy(cp, "/shXXXXXX.tmp", 14); + /* point to the first of six Xes */ + cp += 3; + /* generate random part of filename */ + len = -1; + do { + i = rndget() % 36; + cp[++len] = i < 26 ? 'a' + i : '0' + i - 26; + } while (len < 5); + + /* cyclically attempt to open a temporary file */ + while ((i = open(tp->tffn, O_CREAT | O_EXCL | O_RDWR, 0600)) < 0) { + if (errno != EEXIST) + goto maketemp_out; + /* count down from z to a then from 9 to 0 */ + while (cp[len] == '0') + if (!len--) + goto maketemp_out; + if (cp[len] == 'a') + cp[len] = '9'; + else + --cp[len]; + /* do another cycle */ + } + + if (type == TT_FUNSUB) { + int nfd; + + /* map us high and mark as close-on-exec */ + if ((nfd = savefd(i)) != i) { + close(i); + i = nfd; + } + } + + /* shf_fdopen cannot fail, so no fd leak */ + tp->shf = shf_fdopen(i, SHF_WR, NULL); + + maketemp_out: tp->next = *tlist; *tlist = tp; return (tp); @@ -1503,8 +1691,10 @@ tgrow(struct table *tp) /* 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); + /* table can get very full when reaching its size limit */ + tp->nfree = (tp->tshift == 30) ? 0x3FFF0000UL : + /* but otherwise, only 75% */ + ((i * 3) / 4); tp->tbls = ntblp; if (otblp == NULL) return; @@ -1514,7 +1704,7 @@ tgrow(struct table *tp) if ((tblp = otblp[i]) != NULL) { if ((tblp->flag & DEFINED)) { /* search for free hash table slot */ - j = (perturb = tblp->ua.hval) & mask; + j = perturb = tblp->ua.hval; goto find_first_empty_slot; find_next_empty_slot: j = (j << 2) + j + perturb + 1; @@ -1552,7 +1742,7 @@ ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp) mask = ((size_t)1 << (tp->tshift)) - 1; /* search for hash table slot matching name */ - j = (perturb = h) & mask; + j = perturb = h; goto find_first_slot; find_next_slot: j = (j << 2) + j + perturb + 1; @@ -1662,3 +1852,58 @@ x_sigwinch(int sig MKSH_A_UNUSED) got_winch = 1; } #endif + +#ifdef DF +void +DF(const char *fmt, ...) +{ + va_list args; + struct timeval tv; + mirtime_mjd mjd; + + mksh_lockfd(shl_dbg_fd); + mksh_TIME(tv); + timet2mjd(&mjd, tv.tv_sec); + shf_fprintf(shl_dbg, "[%02u:%02u:%02u (%u) %u.%06u] ", + (unsigned)mjd.sec / 3600, ((unsigned)mjd.sec / 60) % 60, + (unsigned)mjd.sec % 60, (unsigned)getpid(), + (unsigned)tv.tv_sec, (unsigned)tv.tv_usec); + va_start(args, fmt); + shf_vfprintf(shl_dbg, fmt, args); + va_end(args); + shf_putc('\n', shl_dbg); + shf_flush(shl_dbg); + mksh_unlkfd(shl_dbg_fd); +} +#endif + +void +x_mkraw(int fd, mksh_ttyst *ocb, bool forread) +{ + mksh_ttyst cb; + + if (ocb) + mksh_tcget(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; + + mksh_tcset(fd, &cb); +} diff --git a/src/misc.c b/src/misc.c index 4adb7f2..988f98c 100644 --- a/src/misc.c +++ b/src/misc.c @@ -2,7 +2,8 @@ /* $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -29,7 +30,20 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.172 2011/09/07 15:24:18 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.205 2012/12/17 23:18:08 tg Exp $"); + +#define KSH_CHVT_FLAG +#ifdef MKSH_SMALL +#undef KSH_CHVT_FLAG +#endif +#ifdef TIOCSCTTY +#define KSH_CHVT_CODE +#define KSH_CHVT_FLAG +#endif +#ifdef MKSH_LEGACY_MODE +#undef KSH_CHVT_CODE +#undef KSH_CHVT_FLAG +#endif /* type bits for unsigned char */ unsigned char chtypes[UCHAR_MAX + 1]; @@ -38,8 +52,8 @@ 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); -#ifdef TIOCSCTTY +static const unsigned char *cclass(const unsigned char *, unsigned char); +#ifdef KSH_CHVT_CODE static void chvt(const char *); #endif @@ -87,16 +101,16 @@ initctypes(void) chtypes['_'] |= C_ALPHA; setctypes("0123456789", C_DIGIT); /* \0 added automatically */ - setctypes(" \t\n|&;<>()", C_LEX1); + setctypes(TC_LEX1, C_LEX1); setctypes("*@#!$-?", C_VAR1); - setctypes(" \t\n", C_IFSWS); + setctypes(TC_IFSWS, C_IFSWS); setctypes("=-+?", C_SUBOP1); setctypes("\t\n \"#$&'()*;<=>?[\\]`|", C_QUOTE); } /* called from XcheckN() to grow buffer */ char * -Xcheck_grow_(XString *xsp, const char *xp, size_t more) +Xcheck_grow(XString *xsp, const char *xp, size_t more) { const char *old_beg = xsp->beg; @@ -141,12 +155,12 @@ struct options_info { int opts[NELEM(options)]; }; -static char *options_fmt_entry(char *, size_t, int, const void *); +static char *options_fmt_entry(char *, size_t, unsigned int, const void *); static void printoptions(bool); /* format a single select menu item */ static char * -options_fmt_entry(char *buf, size_t buflen, int i, const void *arg) +options_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg) { const struct options_info *oi = (const struct options_info *)arg; @@ -162,7 +176,7 @@ printoptions(bool verbose) size_t i = 0; if (verbose) { - ssize_t n = 0, len, octs = 0; + size_t n = 0, len, octs = 0; struct options_info oi; /* verbose version */ @@ -176,8 +190,8 @@ printoptions(bool verbose) if (len > octs) octs = len; len = utf_mbswidth(options[i].name); - if (len > oi.opt_width) - oi.opt_width = len; + if ((int)len > oi.opt_width) + oi.opt_width = (int)len; } ++i; } @@ -212,19 +226,20 @@ getoptions(void) /* change a Flag(*) value; takes care of special actions */ void -change_flag(enum sh_flag f, int what, unsigned int newval) +change_flag(enum sh_flag f, int what, bool newset) { unsigned char oldval; + unsigned char newval; oldval = Flag(f); - /* needed for tristates */ - Flag(f) = newval ? 1 : 0; + Flag(f) = newval = (newset ? 1 : 0); #ifndef MKSH_UNEMPLOYED if (f == FMONITOR) { if (what != OF_CMDLINE && newval != oldval) j_change(); } else #endif +#ifndef MKSH_NO_CMDLINE_EDITING if (( #if !MKSH_S_NOVI f == FVI || @@ -234,8 +249,10 @@ change_flag(enum sh_flag f, int what, unsigned int newval) Flag(FVI) = #endif Flag(FEMACS) = Flag(FGMACS) = 0; - Flag(f) = (unsigned char)newval; - } else if (f == FPRIVILEGED && oldval && !newval) { + Flag(f) = newval; + } else +#endif + if (f == FPRIVILEGED && oldval && !newval) { /* Turning off -p? */ /*XXX this can probably be optimised */ @@ -249,19 +266,24 @@ change_flag(enum sh_flag f, int what, unsigned int newval) DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid)); #else /* seteuid, setegid, setgid don't EAGAIN on Linux */ - seteuid(ksheuid = kshuid = getuid()); + ksheuid = kshuid = getuid(); +#ifndef MKSH__NO_SETEUGID + seteuid(ksheuid); +#endif DO_SETUID(setuid, (ksheuid)); +#ifndef MKSH__NO_SETEUGID setegid(kshegid); +#endif setgid(kshegid); #endif } else if ((f == FPOSIX || f == FSH) && newval) { Flag(FPOSIX) = Flag(FSH) = Flag(FBRACEEXPAND) = 0; - Flag(f) = (unsigned char)newval; + Flag(f) = newval; } /* Changing interactive flag? */ if (f == FTALKING) { if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid) - Flag(FTALKING_I) = (unsigned char)newval; + Flag(FTALKING_I) = newval; } } @@ -277,7 +299,8 @@ parse_args(const char **argv, { static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */ static char set_opts[NELEM(options) + 6]; /* A:o;s\0 */ - char set, *opts; + bool set; + char *opts; const char *array = NULL; Getopt go; size_t i; @@ -291,7 +314,7 @@ parse_args(const char **argv, /* see cmd_opts[] declaration */ *p++ = 'o'; *p++ = ':'; -#if !defined(MKSH_SMALL) || defined(TIOCSCTTY) +#ifdef KSH_CHVT_FLAG *p++ = 'T'; *p++ = ':'; #endif @@ -332,7 +355,7 @@ parse_args(const char **argv, opts = set_opts; ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT); while ((optc = ksh_getopt(argv, &go, opts)) != -1) { - set = (go.info & GI_PLUS) ? 0 : 1; + set = tobool(!(go.info & GI_PLUS)); switch (optc) { case 'A': if (what == OF_FIRSTTIME) @@ -356,7 +379,7 @@ parse_args(const char **argv, break; } i = option(go.optarg); - if ((i != (size_t)-1) && set == Flag(i)) + if ((i != (size_t)-1) && (set ? 1U : 0U) == Flag(i)) /* * Don't check the context if the flag * isn't changing - makes "set -o interactive" @@ -372,14 +395,14 @@ parse_args(const char **argv, } break; -#if !defined(MKSH_SMALL) || defined(TIOCSCTTY) +#ifdef KSH_CHVT_FLAG case 'T': if (what != OF_FIRSTTIME) break; -#ifndef TIOCSCTTY +#ifndef KSH_CHVT_CODE errorf("no TIOCSCTTY ioctl"); #else - change_flag(FTALKING, OF_CMDLINE, 1); + change_flag(FTALKING, OF_CMDLINE, true); chvt(go.optarg); break; #endif @@ -423,6 +446,7 @@ parse_args(const char **argv, if (arrayset) { const char *ccp = NULL; + mkssert(array != NULL); if (*array) ccp = skip_varname(array, false); if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) { @@ -447,45 +471,40 @@ parse_args(const char **argv, int getn(const char *s, int *ai) { - int i, c, rv = 0; + char c; + unsigned int i = 0; bool neg = false; do { c = *s++; } while (ksh_isspace(c)); - if (c == '-') { + + switch (c) { + case '-': neg = true; + /* FALLTHROUGH */ + case '+': c = *s++; - } else if (c == '+') - c = *s++; - *ai = i = 0; + break; + } + do { if (!ksh_isdigit(c)) - goto getn_out; - i *= 10; - if (i < *ai) - /* overflow */ - goto getn_out; - i += c - '0'; - *ai = i; + /* not numeric */ + return (0); + if (i > 214748364U) + /* overflow on multiplication */ + return (0); + i = i * 10U + (unsigned int)(c - '0'); + /* now: i <= 2147483649U */ } while ((c = *s++)); - rv = 1; - getn_out: - if (neg) - *ai = -*ai; - return (rv); -} + if (i > (neg ? 2147483648U : 2147483647U)) + /* overflow for signed 32-bit int */ + return (0); -/* getn() that prints error */ -int -bi_getn(const char *as, int *ai) -{ - int rv; - - if (!(rv = getn(as, ai))) - bi_errorf("%s: %s", as, "bad number"); - return (rv); + *ai = neg ? -(int)i : (int)i; + return (1); } /** @@ -644,7 +663,7 @@ has_globbing(const char *xp, const char *xpe) if (!in_bracket) { saw_glob = true; in_bracket = true; - if (ISMAGIC(p[1]) && p[2] == NOT) + if (ISMAGIC(p[1]) && p[2] == '!') p += 2; if (ISMAGIC(p[1]) && p[2] == ']') p += 2; @@ -688,7 +707,7 @@ static int do_gmatch(const unsigned char *s, const unsigned char *se, const unsigned char *p, const unsigned char *pe) { - int sc, pc; + unsigned char sc, pc; const unsigned char *prest, *psub, *pnext; const unsigned char *srest; @@ -818,12 +837,13 @@ do_gmatch(const unsigned char *s, const unsigned char *se, } static const unsigned char * -cclass(const unsigned char *p, int sub) +cclass(const unsigned char *p, unsigned char sub) { - int c, d, notp, found = 0; + unsigned char c, d; + bool notp, found = false; const unsigned char *orig_p = p; - if ((notp = (ISMAGIC(*p) && *++p == NOT))) + if ((notp = tobool(ISMAGIC(*p) && *++p == '!'))) p++; do { c = *p++; @@ -857,7 +877,7 @@ cclass(const unsigned char *p, int sub) } else d = c; if (c == sub || (c <= sub && sub <= d)) - found = 1; + found = true; } while (!(ISMAGIC(p[0]) && p[1] == ']')); return ((found != notp) ? p+2 : NULL); @@ -1030,41 +1050,116 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp) * No trailing newline is printed. */ void -print_value_quoted(const char *s) +print_value_quoted(struct shf *shf, const char *s) { - const char *p; - bool inquote = false; + unsigned char c; + const unsigned char *p = (const unsigned char *)s; + bool inquote = true; /* 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; - } + while ((c = *p++) >= 32) + if (ctype(c, C_QUOTE)) + inquote = false; - /* quote via state machine */ - for (p = s; *p; p++) { - if (*p == '\'') { - /* - * 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); - } else if (!inquote) { - shf_putc('\'', shl_stdout); - inquote = true; + p = (const unsigned char *)s; + if (c == 0) { + if (inquote) { + /* nope, use the shortcut */ + shf_puts(s, shf); + return; } - shf_putc(*p, shl_stdout); + + /* otherwise, quote nicely via state machine */ + while ((c = *p++) != 0) { + if (c == '\'') { + /* + * multiple single quotes or any of them + * at the beginning of a string look nicer + * this way than when simply substituting + */ + if (inquote) { + shf_putc('\'', shf); + inquote = false; + } + shf_putc('\\', shf); + } else if (!inquote) { + shf_putc('\'', shf); + inquote = true; + } + shf_putc(c, shf); + } + } else { + unsigned int wc; + size_t n; + + /* use $'...' quote format */ + shf_putc('$', shf); + shf_putc('\'', shf); + while ((c = *p) != 0) { + if (c >= 0xC2) { + n = utf_mbtowc(&wc, (const char *)p); + if (n != (size_t)-1) { + p += n; + shf_fprintf(shf, "\\u%04X", wc); + continue; + } + } + ++p; + switch (c) { + /* see unbksl() in this file for comments */ + case 7: + c = 'a'; + if (0) + /* FALLTHROUGH */ + case '\b': + c = 'b'; + if (0) + /* FALLTHROUGH */ + case '\f': + c = 'f'; + if (0) + /* FALLTHROUGH */ + case '\n': + c = 'n'; + if (0) + /* FALLTHROUGH */ + case '\r': + c = 'r'; + if (0) + /* FALLTHROUGH */ + case '\t': + c = 't'; + if (0) + /* FALLTHROUGH */ + case 11: + c = 'v'; + if (0) + /* FALLTHROUGH */ + case '\033': + /* take E not e because \e is \ in *roff */ + c = 'E'; + /* FALLTHROUGH */ + case '\\': + shf_putc('\\', shf); + + if (0) + /* FALLTHROUGH */ + default: + if (c < 32 || c > 0x7E) { + /* FALLTHROUGH */ + case '\'': + shf_fprintf(shf, "\\%03o", c); + break; + } + + shf_putc(c, shf); + break; + } + } + inquote = true; } if (inquote) - shf_putc('\'', shl_stdout); + shf_putc('\'', shf); } /* @@ -1072,36 +1167,35 @@ print_value_quoted(const char *s) * the i-th element */ void -print_columns(struct shf *shf, int n, - char *(*func)(char *, size_t, int, const void *), +print_columns(struct shf *shf, unsigned int n, + char *(*func)(char *, size_t, unsigned int, const void *), const void *arg, size_t max_oct, size_t max_colz, bool prefcol) { - int i, r, c, rows, cols, nspace, max_col; + unsigned int i, r, c, rows, cols, nspace, max_col; char *str; - if (n <= 0) { + if (!n) + return; + + if (max_colz > 2147483646) { #ifndef MKSH_SMALL - internal_warningf("print_columns called with n=%d <= 0", n); + internal_warningf("print_columns called with %s=%zu >= INT_MAX", + "max_col", max_colz); #endif return; } + max_col = (unsigned int)max_colz; - if (max_colz > 2147483647) { + if (max_oct > 2147483646) { #ifndef MKSH_SMALL - internal_warningf("print_columns called with max_col=%zu > INT_MAX", - max_colz); + internal_warningf("print_columns called with %s=%zu >= INT_MAX", + "max_oct", max_oct); #endif return; } - max_col = (int)max_colz; - ++max_oct; str = alloc(max_oct, ATEMP); - /* ensure x_cols is valid first */ - if (x_cols < MIN_COLS) - change_winsz(); - /* * We use (max_col + 1) to consider the space separator. * Note that no space is printed after the last column @@ -1219,7 +1313,7 @@ reset_nonblock(int fd) char * ksh_get_wd(void) { -#ifdef NO_PATH_MAX +#ifdef MKSH__NO_PATH_MAX char *rv, *cp; if ((cp = get_current_dir_name())) { @@ -1239,6 +1333,10 @@ ksh_get_wd(void) return (rv); } +#ifndef ELOOP +#define ELOOP E2BIG +#endif + char * do_realpath(const char *upath) { @@ -1248,7 +1346,7 @@ do_realpath(const char *upath) size_t len; int llen; struct stat sb; -#ifdef NO_PATH_MAX +#ifdef MKSH__NO_PATH_MAX size_t ldestlen = 0; #define pathlen sb.st_size #define pathcnd (ldestlen < (pathlen + 1)) @@ -1316,7 +1414,7 @@ do_realpath(const char *upath) *xp = '\0'; /* lstat the current output, see if it's a symlink */ - if (lstat(Xstring(xs, xp), &sb)) { + if (mksh_lstat(Xstring(xs, xp), &sb)) { /* lstat failed */ if (errno == ENOENT) { /* because the pathname does not exist */ @@ -1335,6 +1433,7 @@ do_realpath(const char *upath) /* check if we encountered a symlink? */ if (S_ISLNK(sb.st_mode)) { +#ifndef MKSH__NO_SYMLINK /* reached maximum recursion depth? */ if (!symlinks--) { /* yep, prevent infinite loops */ @@ -1344,7 +1443,7 @@ do_realpath(const char *upath) /* get symlink(7) target */ if (pathcnd) { -#ifdef NO_PATH_MAX +#ifdef MKSH__NO_PATH_MAX if (notoktoadd(pathlen, 1)) { errno = ENAMETOOLONG; goto notfound; @@ -1369,7 +1468,9 @@ do_realpath(const char *upath) if (ldest[0] != '/') { /* symlink target is a relative path */ xp = Xrestpos(xs, xp, pos); - } else { + } else +#endif + { /* symlink target is an absolute path */ xp = Xstring(xs, xp); beginning_of_a_pathname: @@ -1730,7 +1831,7 @@ c_cd(const char **wp) return (2); } -#ifdef NO_PATH_MAX +#ifdef MKSH__NO_PATH_MAX /* only a first guess; make_path will enlarge xs if necessary */ XinitN(xs, 1024, ATEMP); #else @@ -1752,7 +1853,7 @@ c_cd(const char **wp) if (cdnode) bi_errorf("%s: %s", dir, "bad directory"); else - bi_errorf("%s: %s", tryp, strerror(errno)); + bi_errorf("%s: %s", tryp, cstrerror(errno)); afree(allocd, ATEMP); Xfree(xs, xp); return (2); @@ -1809,7 +1910,7 @@ c_cd(const char **wp) } -#ifdef TIOCSCTTY +#ifdef KSH_CHVT_CODE extern void chvt_reinit(void); static void @@ -1847,9 +1948,9 @@ chvt(const char *fn) "new shell is potentially insecure, can't revoke", fn); } - if ((fd = open(fn, O_RDWR)) == -1) { + if ((fd = open(fn, O_RDWR)) < 0) { sleep(1); - if ((fd = open(fn, O_RDWR)) == -1) + if ((fd = open(fn, O_RDWR)) < 0) errorf("%s: %s %s", "chvt", "can't open", fn); } switch (fork()) { @@ -1886,19 +1987,6 @@ chvt(const char *fn) #endif #ifdef DEBUG -#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) { @@ -1930,29 +2018,9 @@ strstr(char *b, const char *l) } #endif -#if !HAVE_STRCASESTR -const char * -stristr(const char *b, const char *l) -{ - char first, c; - size_t n; - - if ((first = *l++), ((first = ksh_tolower(first)) == '\0')) - return (b); - n = strlen(l); - stristr_look: - while ((c = *b++), ((c = ksh_tolower(c)) != first)) - if (c == '\0') - return (NULL); - if (strncasecmp(b, l, n)) - goto stristr_look; - return (b - 1); -} -#endif - -#ifdef MKSH_SMALL +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) char * -strndup_(const char *src, size_t len, Area *ap) +strndup_i(const char *src, size_t len, Area *ap) { char *dst = NULL; @@ -1965,9 +2033,9 @@ strndup_(const char *src, size_t len, Area *ap) } char * -strdup_(const char *src, Area *ap) +strdup_i(const char *src, Area *ap) { - return (src == NULL ? NULL : strndup_(src, strlen(src), ap)); + return (src == NULL ? NULL : strndup_i(src, strlen(src), ap)); } #endif diff --git a/src/mksh.1 b/src/mksh.1 index 2c70ef0..51800fc 100644 --- a/src/mksh.1 +++ b/src/mksh.1 @@ -1,8 +1,8 @@ -.\" $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 $ +.\" $MirOS: src/bin/mksh/mksh.1,v 1.305 2013/02/19 18:45:20 tg Exp $ +.\" $OpenBSD: ksh.1,v 1.145 2013/01/17 21:20:25 jmc Exp $ .\"- .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, -.\" 2010, 2011 +.\" 2010, 2011, 2012, 2013 .\" Thorsten Glaser .\" .\" Provided that these terms and disclaimer and all copyright notices @@ -28,6 +28,8 @@ .\" * ~ 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 +.\" * <>| are problematic, so redefine and use \*(Lt\*(Gt\*(Ba +.\" Also make sure to use \& especially with two-letter words. .\" 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. @@ -72,11 +74,13 @@ .\" 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 $ +.Dd $Mdocdate: February 19 2013 $ .\" -.\" Check which macro package we use +.\" Check which macro package we use, and do other -mdoc setup. .\" .ie \n(.g \{\ +. if \*[.T]utf8 .tr \[la]\*(Lt +. if \*[.T]utf8 .tr \[ra]\*(Gt . ie d volume-ds-1 .ds tT gnu . el .ds tT bsd .\} @@ -153,7 +157,7 @@ .Nm .Bk -words .Op Fl +abCefhiklmnprUuvXx -.Op Fl T Ar /dev/ttyCn | \- +.Op Fl T Ar /dev/ttyCn \*(Ba \- .Op Fl +o Ar option .Oo .Fl c Ar string \*(Ba @@ -171,7 +175,24 @@ script use. Its command language is a superset of the .Xr sh C shell language and largely compatible to the original Korn shell. -.Pp +.Ss I'm an Android user, so what's mksh? +.Nm mksh +is a +.Ux +shell / command interpreter, similar to +.Nm COMMAND.COM +or +.Nm CMD.EXE , +which has been included with +.Tn Android Open Source Project +for a while now. +Basically, it's a program that runs in a terminal (console window), +takes user input and runs commands or scripts, which it can also +be asked to do by other programs, even in the background. +Any privilege pop-ups you might be encountering are thus not +.Nm mksh +issues but questions by some other program utilising it. +.Ss Invocation 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 @@ -923,7 +944,7 @@ This even works indirectly: .Bd -literal -offset indent $ bar=foobar; baz=\*(aqf*r\*(aq $ [[ $bar = $baz ]]; echo $? -$ [[ $bar = "$baz" ]]; echo $? +$ [[ $bar = \&"$baz" ]]; echo $? .Ed .Pp Perhaps surprisingly, the first comparison succeeds, @@ -1064,6 +1085,12 @@ 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. +Aliases are specifically an interactive feature: while they do happen +to work in scripts and on the command line in some cases, aliases are +expanded during lexing, so their use must be in a separate command tree +from their definition; otherwise, the alias will not be found. +Noticeably, command lists (separated by semicolon, in command substitutions +also by newline) may be one same parse tree. .Pp The following command aliases are defined automatically by the shell: .Bd -literal -offset indent @@ -1135,9 +1162,23 @@ or command substitutions take the form .Pf $( Ns Ar command Ns \&) or (deprecated) -.Pf \` Ns Ar command Ns \` ; +.Pf \` Ns Ar command Ns \` +or (executed in the current environment) +.Pf ${\ \& Ar command Ns \&;} +and strip trailing newlines; and arithmetic substitutions take the form .Pf $(( Ns Ar expression Ns )) . +Parsing the current-environment command substitution requires a space, +tab or newline after the opening brace and that the closing brace be +recognised as a keyword (i.e. is preceded by a newline or semicolon). +They are also called funsubs (function substitutions) and behave like +functions in that +.Ic local +and +.Ic return +work, and in that +.Ic exit +terminates the parent shell. .Pp If a substitution appears outside of double quotes, the results of the substitution are generally subject to word or field splitting according to @@ -1215,6 +1256,8 @@ 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 \&) +and +.Pf ${\ \& Ar command Ns \&;} substitutions, normal quoting rules are used when .Ar command is parsed; however, for the deprecated @@ -1565,6 +1608,7 @@ begins with it is anchored at the beginning of the value; if it begins with .Ql % , it is anchored at the end. +Patterns that are empty or consist only of wildcards are invalid. A single .Ql / replaces the first occurence of the search @@ -1576,7 +1620,7 @@ is omitted, the .Ar pattern is replaced by the empty string, i.e. deleted. Cannot be applied to a vector. -Inefficiently implemented. +Inefficiently implemented, may be slow. .Pp .Sm off .It Xo @@ -1614,12 +1658,22 @@ Currently, 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 @#} +.It Xo +.Pf ${ Ar name +.Pf @# Ns Oo Ar seed Oc Ns } +.Xc The internal hash of the expansion of -.Ar name . -At the moment, this is NZAT (a never-zero 32-bit hash based on +.Ar name , +with an optional (defaulting to zero) +.Op Ar seed . +At the moment, this is NZAAT (a 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. +.Pp +.It Pf ${ Ns Ar name Ns @Q} +A quoted expression safe for re-entry, whose value is the value of the +.Ar name +parameter, is substituted. .El .Pp Note that @@ -1713,6 +1767,8 @@ 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 BASHPID +The PID of the shell or subshell. .It Ev CDPATH Search path for the .Ic cd @@ -1738,7 +1794,7 @@ 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 +is non-zero and sane enough (minimum is 12x3); similar for .Ev LINES . This parameter is used by the interactive line editing modes, and by the .Ic select , @@ -1746,6 +1802,8 @@ This parameter is used by the interactive line editing modes, and by the and .Ic kill \-l commands to format information columns. +Importing from the environment or unsetting this parameter removes the +binding to the actual terminal size in favour of the provided value. .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. @@ -1793,7 +1851,7 @@ This is different from .Nm ksh . .It Ev HISTSIZE The number of commands normally stored for history. -The default is 500. +The default is 2047. .It Ev HOME The default directory for the .Ic cd @@ -1833,6 +1891,18 @@ executed. .It Ev LINES Set to the number of lines on the terminal or window. Always set, defaults to 24. +See +.Ev COLUMNS . +.It Ev EPOCHREALTIME +Time since the epoch, as returned by +.Xr gettimeofday 2 , +formatted as decimal +.Va tv_sec +followed by a dot +.Pq Sq .\& +and +.Va tv_usec +padded to exactly six decimal digits. .It Ev OLDPWD The previous working directory. Unset if @@ -1943,7 +2013,7 @@ 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, +Due to a strong suggestion from David G. Korn, .Nm now also supports the following form: .Bd -literal -offset indent @@ -2191,6 +2261,9 @@ matches no strings; the pattern matches all strings (think about it). .El .Pp +Note that complicated globbing, especially with alternatives, +is slow; using separate comparisons may (or may not) be faster. +.Pp Note that .Nm mksh .Po and Nm pdksh Pc @@ -2465,14 +2538,8 @@ Grouping operators: ( ) .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 and expressions are calculated using an exactly 32-bit +wide, signed or unsigned, type with silent wraparound on integer overflow. Integer constants may be specified with arbitrary bases using the notation .Ar base Ns # Ns Ar number , where @@ -2480,22 +2547,15 @@ where 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 +Additionally, base-16 integers may be specified by prefixing them with .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 +.Pq case-insensitive +in all forms of arithmetic expressions, except as numeric arguments to the .Ic test -command. +built-in command. +It is discouraged to prefix numbers with a sole zero +.Pq Sq 0 , +because some shells may interpret them as base-8 integers. As a special .Nm mksh extension, numbers to the base of one are treated as either (8-bit @@ -2511,6 +2571,7 @@ instead of .Dq 1#x is also supported. Note that NUL bytes (integral value of zero) cannot be used. +An unset or empty parameter evaluates to 0 in integer context. 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 @@ -2748,7 +2809,8 @@ 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 +uses static scoping (one global scope, one local scope per function) +and allows local variables only on Korn style functions, whereas .Nm mksh uses dynamic scoping (nested scopes of varying locality). Note that special parameters (e.g.\& @@ -2792,7 +2854,7 @@ 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: +In the future, the following differences may also be added: .Bl -bullet .It A separate trap/signal environment will be used during the execution of @@ -2856,8 +2918,8 @@ regular commands .Pp .Ic \&[ , chdir , bind , cat , .Ic echo , let , mknod , print , -.Ic printf , pwd , realpath , rename , -.Ic sleep , test , ulimit , whence +.Ic pwd , realpath , rename , sleep , +.Ic test , ulimit , whence .Pp In the future, the additional .Nm @@ -3224,8 +3286,6 @@ defaults to 1. .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 @@ -3294,12 +3354,13 @@ 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 +If no parameters are specified, all parameters with the export attribute +set are printed one per line; either their names, or, if a +.Ql \- +with no option letter is specified, name=value pairs, or, with +.Fl p , .Ic export -commands defining all exported parameters, including their values, are printed. +commands suitable for re-entry. .Pp .It Ic false A command that exits with a non-zero status. @@ -3360,11 +3421,8 @@ The meaning of and .Fl s is identical: re-execute the selected command without invoking an editor. -This command is usually accessed with the predefined +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. @@ -3548,7 +3606,7 @@ The file type may be (character type device), or .Cm p -(named pipe). +.Pq named pipe , Tn FIFO . The file created may be modified according to its .Ar mode (via the @@ -3619,15 +3677,16 @@ 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 +Approximately the same as the +.Xr printf 1 , +utility, except it uses the same .Sx Backslash expansion -and I/O code as the rest of +and I/O code and does hot handle floating point 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. +Do not use in new code. .Pp .It Ic pwd Op Fl LP Print the present working directory. @@ -3650,7 +3709,7 @@ directories to the root directory) is printed. .Pp .It Xo .Ic read -.Op Fl A | Fl a +.Op Fl A \*(Ba Fl a .Op Fl d Ar x .Oo Fl N Ar z \*(Ba .Fl n Ar z Oc @@ -3769,7 +3828,7 @@ 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#./}>" + print \-r \-\- "found \*(Lt${filename#./}\*(Gt" done .Ed .Pp @@ -4295,7 +4354,8 @@ instead of .Ql xtrace . .It Fl p Ar file .Ar file -is a named pipe. +is a named pipe +.Pq Tn FIFO . .It Fl r Ar file .Ar file exists and is readable. @@ -4393,22 +4453,22 @@ a mathematical term or the name of an integer variable: 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 +Note that some special rules are applied (courtesy of +.Px +) if the number of arguments to .Ic test -or +or inside the brackets .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 \&! ) . +arguments can be stripped such that only one to three arguments remain, +then the lowered comparison is executed; (thanks to XSI) parentheses +.Ic \e( ... \e) +lower four- and three-argument forms to two- and one-argument forms, +respectively; three-argument forms ultimately prefer binary operations, +followed by negation and parenthesis lowering; two- and four-argument forms +prefer negation followed by parenthesis; the one-argument form always implies +.Fl n . .Pp .Sy Note : A common mistake is to use @@ -4430,7 +4490,11 @@ instead, or the double-bracket operator or, to avoid pattern matching (see .Ic \&[[ above): -.Dq if \&[[ $foo = "$bar" \&]] +.Dq if \&[[ $foo = \&"$bar" \&]] +.Pp +The +.Ic \&[[ ... ]] +construct is not only more secure to use but also often faster. .Pp .It Xo .Ic time @@ -4600,6 +4664,14 @@ If is used inside a function, any parameters specified are localised. This is not done by the otherwise identical .Ic global . +.Em Note : +This means that +.Nm No 's Ic global +command is +.Em not +equivalent to other programming languages' as it does not allow a +function called from another function to access a parameter at truly +global scope, but only prevents putting an accessed one into local scope. .Pp When .Fl f @@ -6128,7 +6200,6 @@ contains the system and suid profile. .Xr cat 1 , .Xr ed 1 , .Xr getopt 1 , -.Xr printf 1 , .Xr sed 1 , .Xr sh 1 , .Xr stty 1 , @@ -6155,6 +6226,8 @@ contains the system and suid profile. .Xr mknod 8 .Pp .Pa http://docsrv.sco.com:507/en/man/html.C/sh.C.html +.Pp +.Pa https://www.mirbsd.org/ksh\-chan.htm .Rs .%A Morris Bolsky .%B "The KornShell Command and Programming Language" @@ -6262,19 +6335,27 @@ $ /bin/sleep 666 && echo fubar .Ed .Pp This document attempts to describe -.Nm mksh\ R40+CVS +.Nm mksh\ R43 and up, compiled without any options impacting functionality, such as .Dv MKSH_SMALL , +when not called as +.Pa /bin/sh +which, on some systems only, enables +.Ic set \-o sh +automatically (whose behaviour differs across targets), 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 +.Aq miros\-mksh@mirbsd.org or in the .Li \&#\&!/bin/mksh .Pq or Li \&#ksh IRC channel at -.Pa irc.freenode.net:6667 . +.Pa irc.freenode.net +.Pq Port 6697 SSL, 6667 unencrypted , +or at: +.Pa https://launchpad.net/mksh diff --git a/src/sh.h b/src/sh.h index 740518c..c358058 100644 --- a/src/sh.h +++ b/src/sh.h @@ -1,15 +1,16 @@ -/* $OpenBSD: sh.h,v 1.30 2010/01/04 18:07:11 deraadt Exp $ */ +/* $OpenBSD: sh.h,v 1.31 2012/09/10 01:25:30 tedu Exp $ */ /* $OpenBSD: shf.h,v 1.6 2005/12/11 18:53:51 deraadt Exp $ */ -/* $OpenBSD: table.h,v 1.7 2005/12/11 20:31:21 otto Exp $ */ +/* $OpenBSD: table.h,v 1.8 2012/02/19 07:52:30 otto Exp $ */ /* $OpenBSD: tree.h,v 1.10 2005/03/28 21:28:22 deraadt Exp $ */ /* $OpenBSD: expand.h,v 1.6 2005/03/30 17:16:37 deraadt Exp $ */ -/* $OpenBSD: lex.h,v 1.11 2006/05/29 18:22:24 otto Exp $ */ -/* $OpenBSD: proto.h,v 1.33 2010/05/19 17:36:08 jasper Exp $ */ +/* $OpenBSD: lex.h,v 1.12 2013/01/20 14:47:46 stsp Exp $ */ +/* $OpenBSD: proto.h,v 1.34 2012/06/27 07:17:19 otto Exp $ */ /* $OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $ */ /* $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $ */ /*- - * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -37,7 +38,14 @@ #include #endif #include +#if HAVE_BOTH_TIME_H #include +#include +#elif HAVE_SYS_TIME_H +#include +#elif HAVE_TIME_H +#include +#endif #include #if HAVE_SYS_SYSMACROS_H #include @@ -48,7 +56,9 @@ #if HAVE_SYS_MMAN_H #include #endif +#if HAVE_SYS_RESOURCE_H #include +#endif #include #include #include @@ -78,8 +88,16 @@ #if HAVE_STRINGS_H #include #endif +#if HAVE_TERMIOS_H #include -#include +#else +/* shudder… */ +#include +#endif +#ifdef _ISC_UNIX +/* XXX imake style */ +#include +#endif #if HAVE_ULIMIT_H #include #endif @@ -99,11 +117,6 @@ #else #define MKSH_A_FORMAT(x,y,z) /* nothing */ #endif -#if HAVE_ATTRIBUTE_NONNULL -#define MKSH_A_NONNULL(a) __attribute__(a) -#else -#define MKSH_A_NONNULL(a) /* nothing */ -#endif #if HAVE_ATTRIBUTE_NORETURN #define MKSH_A_NORETURN __attribute__((__noreturn__)) #else @@ -151,9 +164,85 @@ #endif #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.495 2011/10/07 19:51:44 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.639 2013/02/19 18:45:22 tg Exp $"); #endif -#define MKSH_VERSION "R40 2011/10/07" +#define MKSH_VERSION "R43 2013/02/19" + +/* arithmetic types: C implementation */ +#if !HAVE_CAN_INTTYPES +#if !HAVE_CAN_UCBINTS +typedef signed int int32_t; +typedef unsigned int uint32_t; +#else +typedef u_int32_t uint32_t; +#endif +#endif + +/* arithmetic types: shell arithmetics */ +#ifdef MKSH_LEGACY_MODE +/* + * POSIX demands these to be the C environment's long type + */ +typedef long mksh_ari_t; +typedef unsigned long mksh_uari_t; +#else +/* + * These types are exactly 32 bit wide; signed and unsigned + * integer wraparound, even across division and modulo, for + * any shell code using them, is guaranteed. + */ +typedef int32_t mksh_ari_t; +typedef uint32_t mksh_uari_t; +#endif + +/* boolean type (no deliberately) */ +typedef unsigned char mksh_bool; +#undef bool +/* false MUST equal the same 0 as written by static storage initialisation */ +#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) + +/* char (octet) type: C implementation */ +#if !HAVE_CAN_INT8TYPE +#if !HAVE_CAN_UCBINT8 +typedef unsigned char uint8_t; +#else +typedef u_int8_t uint8_t; +#endif +#endif + +/* other standard types */ + +#if !HAVE_RLIM_T +typedef unsigned long rlim_t; +#endif + +#if !HAVE_SIG_T +#undef sig_t +typedef void (*sig_t)(int); +#endif + +#ifdef MKSH_TYPEDEF_SIG_ATOMIC_T +typedef MKSH_TYPEDEF_SIG_ATOMIC_T sig_atomic_t; +#endif + +#ifdef MKSH_TYPEDEF_SSIZE_T +typedef MKSH_TYPEDEF_SSIZE_T ssize_t; +#endif + +/* un-do vendor damage */ + +#undef BAD /* AIX defines that somewhere */ +#undef PRINT /* LynxOS defines that somewhere */ +#undef flock /* SCO UnixWare defines that to flock64 but ENOENT */ + #ifndef MKSH_INCLUDES_ONLY @@ -173,32 +262,6 @@ struct rusage { }; #endif -#if !HAVE_RLIM_T -typedef long rlim_t; -#endif - -#if !HAVE_SIG_T -#undef sig_t -typedef void (*sig_t)(int); -#endif - -#if !HAVE_CAN_INTTYPES -#if !HAVE_CAN_UCBINTS -typedef signed int int32_t; -typedef unsigned int uint32_t; -#else -typedef u_int32_t uint32_t; -#endif -#endif - -#if !HAVE_CAN_INT8TYPE -#if !HAVE_CAN_UCBINT8 -typedef unsigned char uint8_t; -#else -typedef u_int8_t uint8_t; -#endif -#endif - /* extra macros */ #ifndef timerclear @@ -237,8 +300,10 @@ typedef u_int8_t uint8_t; #define ksh_toupper(c) (((c) >= 'a') && ((c) <= 'z') ? (c) - 'a' + 'A' : (c)) #define ksh_isdash(s) (((s) != NULL) && ((s)[0] == '-') && ((s)[1] == '\0')) #define ksh_isspace(c) ((((c) >= 0x09) && ((c) <= 0x0D)) || ((c) == 0x20)) +#define ksh_min(x,y) ((x) < (y) ? (x) : (y)) +#define ksh_max(x,y) ((x) > (y) ? (x) : (y)) -#ifdef NO_PATH_MAX +#ifdef MKSH__NO_PATH_MAX #undef PATH_MAX #else #ifndef PATH_MAX @@ -265,24 +330,16 @@ typedef u_int8_t uint8_t; #define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) #endif -#if !defined(MAP_FAILED) -/* XXX imake style */ -# if defined(__linux) -#define MAP_FAILED ((void *)-1) -# elif defined(__bsdi__) || defined(__osf__) || defined(__ultrix) -#define MAP_FAILED ((caddr_t)-1) -# endif -#endif - #ifndef NSIG #if defined(_NSIG) #define NSIG _NSIG #elif defined(SIGMAX) #define NSIG (SIGMAX+1) +#elif defined(_SIGMAX) +#define NSIG (_SIGMAX+1) #endif #endif -#undef BAD /* AIX defines that somewhere */ /* OS-dependent additions (functions, variables, by OS) */ @@ -290,31 +347,40 @@ typedef u_int8_t uint8_t; extern int flock(int, int); #endif +#if !HAVE_GETTIMEOFDAY +#define mksh_TIME(tv) do { \ + (tv).tv_usec = 0; \ + (tv).tv_sec = time(NULL); \ +} while (/* CONSTCOND */ 0) +#else +#define mksh_TIME(tv) gettimeofday(&(tv), NULL) +#endif + #if !HAVE_GETRUSAGE extern int getrusage(int, struct rusage *); #endif +#if !HAVE_MEMMOVE +/* we assume either memmove or bcopy exist, at the moment */ +#define memmove(dst, src, len) bcopy((src), (dst), (len)) +#endif + #if !HAVE_REVOKE_DECL extern int revoke(const char *); #endif -#ifdef __ultrix -/* XXX imake style */ -int strcasecmp(const char *, const char *); -#endif - -#if !HAVE_STRCASESTR -const char *stristr(const char *, const char *); +#if defined(DEBUG) || !HAVE_STRERROR +#define strerror /* poisoned */ dontuse_strerror +#define cstrerror /* replaced */ cstrerror +extern const char *cstrerror(int); +#else +#define cstrerror(errnum) ((const char *)strerror(errnum)) #endif #if !HAVE_STRLCPY size_t strlcpy(char *, const char *, size_t); #endif -#if !HAVE_SYS_SIGLIST_DECL -extern const char *const sys_siglist[]; -#endif - #ifdef __INTERIX /* XXX imake style */ #define makedev mkdev @@ -322,9 +388,34 @@ extern int __cdecl seteuid(uid_t); extern int __cdecl setegid(gid_t); #endif -/* remove redundances */ +#if defined(__COHERENT__) +#ifndef O_ACCMODE +/* this need not work everywhere, take care */ +#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) +#endif +#endif -#if defined(MirBSD) && (MirBSD >= 0x08A8) +#ifdef MKSH__NO_SYMLINK +#undef S_ISLNK +#define S_ISLNK(m) (/* CONSTCOND */ 0) +#define mksh_lstat stat +#else +#define mksh_lstat lstat +#endif + +#if HAVE_TERMIOS_H +#define mksh_ttyst struct termios +#define mksh_tcget(fd,st) tcgetattr((fd), (st)) +#define mksh_tcset(fd,st) tcsetattr((fd), TCSADRAIN, (st)) +#else +#define mksh_ttyst struct termio +#define mksh_tcget(fd,st) ioctl((fd), TCGETA, (st)) +#define mksh_tcset(fd,st) ioctl((fd), TCSETAW, (st)) +#endif + +/* remove redundancies */ + +#if defined(MirBSD) && (MirBSD >= 0x08A8) && !defined(MKSH_OPTSTATIC) #define MKSH_mirbsd_wcwidth #define utf_wcwidth(i) wcwidth((__WCHAR_TYPE__)i) extern int wcwidth(__WCHAR_TYPE__); @@ -344,46 +435,25 @@ extern int wcwidth(__WCHAR_TYPE__); #define BIT(i) (1 << (i)) #define NELEM(a) (sizeof(a) / sizeof((a)[0])) -/* arithmetics types */ -typedef int32_t mksh_ari_t; -typedef uint32_t mksh_uari_t; - -/* boolean type (no 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 */ -#define FDBASE 10 /* First file usable by Shell */ -#else -#define NUFILE 56 /* Number of user-accessible files */ -#define FDBASE 24 /* First file usable by Shell */ -#endif - /* * 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). + * portability problems (calling strchr(x, 0x80 | 'x') is error prone). */ #define MAGIC (7) /* prefix for *?[!{,} during expand */ #define ISMAGIC(c) ((unsigned char)(c) == MAGIC) -#define NOT '!' /* might use ^ (ie, [!...] vs [^..]) */ #define LINE 4096 /* input line size */ EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */ -EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION); + +#ifdef MKSH_LEGACY_MODE +#define KSH_VERSIONNAME "LEGACY" +#else +#define KSH_VERSIONNAME "MIRBSD" +#endif +EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME \ + " KSH " MKSH_VERSION); #define KSH_VERSION (initvsn + /* "KSH_VERSION=@(#)" */ 16) EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); @@ -425,13 +495,30 @@ char *ucstrstr(char *, const char *); }) #define vstrchr(s,c) (cstrchr((s), (c)) != NULL) #define vstrstr(b,l) (cstrstr((b), (l)) != NULL) -#define mkssert(e) ((e) ? (void)0 : exit(255)) #else /* !DEBUG, !gcc */ #define cstrchr(s,c) ((const char *)strchr((s), (c))) #define cstrstr(s,c) ((const char *)strstr((s), (c))) #define vstrchr(s,c) (strchr((s), (c)) != NULL) #define vstrstr(b,l) (strstr((b), (l)) != NULL) -#define mkssert(e) ((void)0) +#endif + +#if defined(DEBUG) || defined(__COVERITY__) +#define mkssert(e) do { if (!(e)) exit(255); } while (/* CONSTCOND */ 0) +#ifndef DEBUG_LEAKS +#define DEBUG_LEAKS +#endif +#else +#define mkssert(e) do { } while (/* CONSTCOND */ 0) +#endif + +#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 431) +#error Must run Build.sh to compile this. +int +im_sorry_dave(void) +{ + /* I’m sorry, Dave. I’m afraid I can’t do that. */ + return (thiswillneverbedefinedIhope()); +} #endif /* use this ipv strchr(s, 0) but no side effects in s! */ @@ -441,12 +528,12 @@ char *ucstrstr(char *, const char *); (dst) = (src) + utf_ptradj(src); \ } while (/* CONSTCOND */ 0) -#ifdef MKSH_SMALL -#define strdupx(d, s, ap) do { \ - (d) = strdup_((s), (ap)); \ +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) +#define strdupx(d, s, ap) do { \ + (d) = strdup_i((s), (ap)); \ } while (/* CONSTCOND */ 0) -#define strndupx(d, s, n, ap) do { \ - (d) = strndup_((s), (n), (ap)); \ +#define strndupx(d, s, n, ap) do { \ + (d) = strndup_i((s), (n), (ap)); \ } while (/* CONSTCOND */ 0) #else /* be careful to evaluate arguments only once! */ @@ -475,8 +562,15 @@ char *ucstrstr(char *, const char *); } while (/* CONSTCOND */ 0) #endif -#if HAVE_STRCASESTR -#define stristr(b,l) ((const char *)strcasestr((b), (l))) +#ifdef MKSH_LEGACY_MODE +#ifndef MKSH_NO_CMDLINE_EDITING +#define MKSH_NO_CMDLINE_EDITING /* defined */ +#endif +#ifndef MKSH_CONSERVATIVE_FDS +#define MKSH_CONSERVATIVE_FDS /* defined */ +#endif +#undef MKSH_S_NOVI +#define MKSH_S_NOVI 1 #endif #ifdef MKSH_SMALL @@ -499,6 +593,15 @@ char *ucstrstr(char *, const char *); #define MKSH_UNEMPLOYED 1 #endif +/* these shall be smaller than 100 */ +#ifdef MKSH_CONSERVATIVE_FDS +#define NUFILE 32 /* Number of user-accessible files */ +#define FDBASE 10 /* First file usable by Shell */ +#else +#define NUFILE 56 /* Number of user-accessible files */ +#define FDBASE 24 /* First file usable by Shell */ +#endif + /* * simple grouping allocator */ @@ -521,12 +624,7 @@ char *ucstrstr(char *, const char *); #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 +#ifdef MKSH__NO_PATH_MAX /* GNU libc: get_current_dir_name(3) -> free(3) */ #define free_gnu_gcdn(p) free(p) #endif @@ -563,7 +661,25 @@ enum sh_flag { /* * parsing & execution environment + * + * note that kshlongjmp MUST NOT be passed 0 as second argument! */ +#ifdef MKSH_NO_SIGSETJMP +#define kshjmp_buf jmp_buf +#define kshsetjmp(jbuf) _setjmp(jbuf) +#define kshlongjmp _longjmp +#else +#define kshjmp_buf sigjmp_buf +#define kshsetjmp(jbuf) sigsetjmp((jbuf), 0) +#define kshlongjmp siglongjmp +#endif + +struct sretrace_info; +struct yyrecursive_state; + +EXTERN struct sretrace_info *retrace_info E_INIT(NULL); +EXTERN int subshell_nesting_type E_INIT(0); + extern struct env { ALLOC_ITEM alloc_INT; /* internal, do not touch */ Area area; /* temporary allocation area */ @@ -571,9 +687,11 @@ extern struct env { struct block *loc; /* local variables and functions */ short *savefd; /* original redirected fds */ struct temp *temps; /* temp files */ - sigjmp_buf jbuf; /* long jump back to env creator */ - short type; /* environment type - see below */ - short flags; /* EF_* */ + /* saved parser recursion state */ + struct yyrecursive_state *yyrecursive_statep; + kshjmp_buf jbuf; /* long jump back to env creator */ + uint8_t type; /* environment type - see below */ + uint8_t flags; /* EF_* */ } *e; /* struct env.type values */ @@ -584,20 +702,21 @@ extern struct env { #define E_EXEC 4 /* executing command tree */ #define E_LOOP 5 /* executing for/while # */ #define E_ERRH 6 /* general error handler # */ +#define E_GONE 7 /* hidden in child */ /* # indicates env has valid jbuf (see unwind()) */ /* struct env.flag values */ -#define EF_FUNC_PARSE BIT(0) /* function being parsed */ #define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */ #define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */ /* Do breaks/continues stop at env type e? */ -#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE \ - || (t) == E_FUNC || (t) == E_INCL) +#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE || \ + (t) == E_FUNC || (t) == E_INCL) /* Do returns stop at env type e? */ #define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL) -/* values for siglongjmp(e->jbuf, 0) */ +/* values for kshlongjmp(e->jbuf, i) */ +/* note that i MUST NOT be zero */ #define LRETURN 1 /* return statement */ #define LEXIT 2 /* exit statement */ #define LERROR 3 /* errorf() called */ @@ -675,23 +794,36 @@ 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 Tsgunset[] E_INIT("*=unset"); +#define Tunset (Tsgunset + 2) /* "unset" */ +EXTERN const char Tsgexport[] E_INIT("*=export"); +#define Texport (Tsgexport + 2) /* "export" */ +EXTERN const char Tsgreadonly[] E_INIT("*=readonly"); +#define Treadonly (Tsgreadonly + 2) /* "readonly" */ 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" */ +EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n"); +#define TC_IFSWS (TC_LEX1 + 7) /* space tab newline */ + +typedef uint8_t Temp_type; +/* expanded heredoc */ +#define TT_HEREDOC_EXP 0 +/* temporary file used for history editing (fc -e) */ +#define TT_HIST_EDIT 1 +/* temporary file used during in-situ command substitution */ +#define TT_FUNSUB 2 -enum temp_type { - TT_HEREDOC_EXP, /* expanded heredoc */ - TT_HIST_EDIT /* temp file used for history editing (fc -e) */ -}; -typedef enum temp_type Temp_type; /* temp/heredoc files. The file is removed when the struct is freed. */ struct temp { struct temp *next; struct shf *shf; - char *name; - int pid; /* pid of process parsed here-doc */ + /* pid of process parsed here-doc */ + pid_t pid; Temp_type type; + /* actually longer: name (variable length) */ + char tffn[3]; }; /* @@ -701,6 +833,9 @@ 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]) +#ifdef DF +#define shl_dbg (&shf_iob[3]) /* for DF() */ +#endif EXTERN bool shl_stdout_ok; /* @@ -767,7 +902,7 @@ EXTERN unsigned int ksh_tmout; EXTERN enum tmout_enum ksh_tmout_state E_INIT(TMOUT_EXECUTING); /* For "You have stopped jobs" message */ -EXTERN int really_exit; +EXTERN bool really_exit; /* * fast character classes @@ -860,32 +995,25 @@ EXTERN char *current_wd; #define MIN_COLS (2 + MIN_EDIT_SPACE + 3) #define MIN_LINS 3 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 '(' -#define CPAREN ')' -#define OBRACK '[' -#define CBRACK ']' -#define OBRACE '{' -#define CBRACE '}' +EXTERN mksh_ari_t x_lins E_INIT(24); /* tty lines */ /* Determine the location of the system (common) profile */ -/* This is deliberately not configurable via CPPFLAGS */ +#ifndef MKSH_DEFAULT_PROFILEDIR #if defined(ANDROID) -#define MKSH_ETC_LOCATION "/system/etc" +#define MKSH_DEFAULT_PROFILEDIR "/system/etc" #else -#define MKSH_ETC_LOCATION "/etc" +#define MKSH_DEFAULT_PROFILEDIR "/etc" +#endif #endif -#define MKSH_SYSTEM_PROFILE MKSH_ETC_LOCATION "/profile" -#define MKSH_SUID_PROFILE MKSH_ETC_LOCATION "/suid_profile" +#define MKSH_SYSTEM_PROFILE MKSH_DEFAULT_PROFILEDIR "/profile" +#define MKSH_SUID_PROFILE MKSH_DEFAULT_PROFILEDIR "/suid_profile" /* Used by v_evaluate() and setstr() to control action when error occurs */ -#define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */ +#define KSH_UNWIND_ERROR 0 /* unwind the stack (kshlongjmp) */ #define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */ /* @@ -896,10 +1024,10 @@ EXTERN mksh_ari_t x_lins E_INIT(-1); /* tty lines */ #define shf_fileno(shf) ((shf)->fd) #define shf_setfileno(shf,nfd) ((shf)->fd = (nfd)) -#define shf_getc_(shf) ((shf)->rnleft > 0 ? \ +#define shf_getc_i(shf) ((shf)->rnleft > 0 ? \ (shf)->rnleft--, *(shf)->rp++ : \ shf_getchar(shf)) -#define shf_putc_(c, shf) ((shf)->wnleft == 0 ? \ +#define shf_putc_i(c, shf) ((shf)->wnleft == 0 ? \ shf_putchar((c), (shf)) : \ ((shf)->wnleft--, *(shf)->wp++ = (c))) #define shf_eof(shf) ((shf)->flags & SHF_EOF) @@ -989,6 +1117,8 @@ struct tbl { char name[4]; }; +EXTERN struct tbl vtemp; + /* common flag bits */ #define ALLOC BIT(0) /* val.s has been allocated */ #define DEFINED BIT(1) /* is defined in block */ @@ -1195,6 +1325,7 @@ struct op { #define SPAT 10 /* separate pattern: | */ #define CPAT 11 /* close pattern: ) */ #define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */ +#define FUNSUB 14 /* ${ foo;} substitution (NUL terminated) */ /* * IO redirection @@ -1230,7 +1361,6 @@ struct ioword { #define XBGND BIT(2) /* command & */ #define XPIPEI BIT(3) /* input is pipe */ #define XPIPEO BIT(4) /* output is pipe */ -#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */ #define XXCOM BIT(5) /* `...` command */ #define XPCLOSE BIT(6) /* exchild: close close_fd in parent */ #define XCCLOSE BIT(7) /* exchild: close close_fd in child */ @@ -1254,6 +1384,9 @@ struct ioword { #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 */ +#if !defined(MKSH_SMALL) +#define DOTCOMEXEC BIT(11) /* not an eval flag, used by sh -c hack */ +#endif /* * The arguments of [[ .. ]] expressions are kept in t->args[] and flags @@ -1297,7 +1430,7 @@ typedef char *XStringP; #define XcheckN(xs, xp, n) do { \ ssize_t more = ((xp) + (n)) - (xs).end; \ if (more > 0) \ - (xp) = Xcheck_grow_(&(xs), (xp), more); \ + (xp) = Xcheck_grow(&(xs), (xp), more); \ } while (/* CONSTCOND */ 0) /* check for overflow, expand string */ @@ -1318,41 +1451,44 @@ typedef char *XStringP; #define Xsavepos(xs, xp) ((xp) - (xs).beg) #define Xrestpos(xs, xp, n) ((xs).beg + (n)) -char *Xcheck_grow_(XString *, const char *, size_t); +char *Xcheck_grow(XString *, const char *, size_t); /* * expandable vector of generic pointers */ -typedef struct XPtrV { - void **cur; /* next avail pointer */ - void **beg, **end; /* begin, end of vector */ +typedef struct { + /* begin of allocated area */ + void **beg; + /* currently used number of entries */ + size_t len; + /* allocated number of entries */ + size_t siz; } XPtrV; -#define XPinit(x, n) do { \ - 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 XPinit(x, n) do { \ + (x).siz = (n); \ + (x).len = 0; \ + (x).beg = alloc2((x).siz, sizeof(void *), ATEMP); \ +} while (/* CONSTCOND */ 0) \ -#define XPput(x, p) do { \ - if ((x).cur >= (x).end) { \ - size_t n = XPsize(x); \ - (x).beg = aresize2((x).beg, \ - n, 2 * sizeof(void *), ATEMP); \ - (x).cur = (x).beg + n; \ - (x).end = (x).cur + n; \ +#define XPput(x, p) do { \ + if ((x).len == (x).siz) { \ + (x).beg = aresize2((x).beg, (x).siz, \ + 2 * sizeof(void *), ATEMP); \ + (x).siz <<= 1; \ } \ - *(x).cur++ = (p); \ + (x).beg[(x).len++] = (p); \ } while (/* CONSTCOND */ 0) #define XPptrv(x) ((x).beg) -#define XPsize(x) ((x).cur - (x).beg) +#define XPsize(x) ((x).len) #define XPclose(x) aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP) #define XPfree(x) afree((x).beg, ATEMP) -#define IDENT 64 +/* + * Lexer internals + */ typedef struct source Source; struct source { @@ -1386,6 +1522,7 @@ struct source { #define SWORDSEP 6 /* string[] separator */ #define SALIAS 7 /* alias expansion */ #define SREREAD 8 /* read ahead to be re-scanned */ +#define SSTRINGCMDLINE 9 /* string from "mksh -c ..." */ /* Source.flags values */ #define SF_ECHO BIT(0) /* echo input to shlout */ @@ -1393,6 +1530,9 @@ struct source { #define SF_ALIASEND BIT(2) /* faking space at end of alias */ #define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */ #define SF_HASALIAS BIT(4) /* u.tblp valid (SALIAS, SEOF) */ +#if !defined(MKSH_SMALL) +#define SF_MAYEXEC BIT(5) /* special sh -c optimisation hack */ +#endif typedef union { int i; @@ -1444,7 +1584,7 @@ typedef union { #define CMDWORD BIT(8) /* parsing simple command (alias related) */ #define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */ #define LQCHAR BIT(10) /* source string contains QCHAR */ -#define HEREDOC BIT(11) /* parsing a here document */ +#define HEREDOC BIT(11) /* parsing a here document body */ #define HERES 10 /* max number of << in line */ @@ -1452,23 +1592,25 @@ typedef union { #define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ #define UNCTRL(x) ((x) ^ 0x40) /* ASCII */ +#define IDENT 64 + EXTERN Source *source; /* yyparse/yylex source */ EXTERN YYSTYPE yylval; /* result from yylex */ EXTERN struct ioword *heres[HERES], **herep; -EXTERN char ident[IDENT+1]; +EXTERN char ident[IDENT + 1]; -#define HISTORYSIZE 500 /* size of saved history */ - -EXTERN char **history; /* saved commands */ -EXTERN char **histptr; /* last history item */ -EXTERN int histsize; /* history size */ +EXTERN char **history; /* saved commands */ +EXTERN char **histptr; /* last history item */ +EXTERN mksh_ari_t 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 notok2mul(max, val, c) (((val) != 0) && ((c) != 0) && \ + (((max) / (c)) < (val))) +#define notok2add(max, val, c) ((val) > ((max) - (c))) +#define notoktomul(val, cnst) notok2mul(SIZE_MAX, (val), (cnst)) +#define notoktoadd(val, cnst) notok2add(SIZE_MAX, (val), (cnst)) #define checkoktoadd(val, cnst) do { \ if (notoktoadd((val), (cnst))) \ internal_errorf(Tintovfl, (size_t)(val), \ @@ -1476,7 +1618,7 @@ EXTERN struct timeval j_usrtime, j_systime; } while (/* CONSTCOND */ 0) -/* NZAT/NZAAT hashes based on Bob Jenkins' one-at-a-time hash */ +/* NZAAT hash 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 $ */ @@ -1509,15 +1651,6 @@ EXTERN struct timeval j_usrtime, j_systime; 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; \ @@ -1537,14 +1670,19 @@ 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_NO_CMDLINE_EDITING #ifndef MKSH_SMALL int x_bind(const char *, const char *, bool, bool); #else int x_bind(const char *, const char *, bool); #endif void x_init(void); -void x_mkraw(int, struct termios *, bool); +#ifdef DEBUG_LEAKS +void x_done(void); +#endif int x_read(char *, size_t); +#endif +void x_mkraw(int, mksh_ttyst *, bool); /* eval.c */ char *substitute(const char *, int); char **eval(const char **, int); @@ -1552,7 +1690,8 @@ char *evalstr(const char *cp, int); char *evalonestr(const char *cp, int); char *debunk(char *, const char *, size_t); void expand(const char *, XPtrV *, int); -int glob_str(char *, XPtrV *, int); +int glob_str(char *, XPtrV *, bool); +char *tilde(char *); /* exec.c */ int execute(struct op * volatile, volatile int, volatile int * volatile); int shcomexec(const char **); @@ -1562,8 +1701,8 @@ const char *builtin(const char *, int (*)(const char **)); struct tbl *findcom(const char *, 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 *); +void pr_menu(const char * const *); +void pr_list(char * const *); /* expr.c */ int evaluate(const char *, mksh_ari_t *, int, bool); int v_evaluate(struct tbl *, const char *, volatile int, bool); @@ -1578,6 +1717,7 @@ size_t utf_ptradj(const char *); int utf_wcwidth(unsigned int); #endif int ksh_access(const char *, int); +struct tbl *tempvar(void); /* funcs.c */ int c_hash(const char **); int c_pwd(const char **); @@ -1598,7 +1738,9 @@ int c_fgbg(const char **); int c_kill(const char **); void getopts_reset(int); int c_getopts(const char **); +#ifndef MKSH_NO_CMDLINE_EDITING int c_bind(const char **); +#endif int c_shift(const char **); int c_umask(const char **); int c_dot(const char **); @@ -1636,17 +1778,19 @@ void histsave(int *, const char *, bool, bool); bool histsync(void); #endif int c_fc(const char **); -void sethistsize(int); +void sethistsize(mksh_ari_t); #if HAVE_PERSISTENT_HISTORY void sethistfile(const char *); #endif +#if !defined(MKSH_NO_CMDLINE_EDITING) && !MKSH_S_NOVI char **histpos(void); int histnum(int); +#endif int findhist(int, int, const char *, int); char **hist_get_newest(bool); void inittraps(void); void alarm_init(void); -Trap *gettrap(const char *, int); +Trap *gettrap(const char *, bool); void trapsig(int); void intrcheck(void); int fatal_trap_check(void); @@ -1660,6 +1804,10 @@ int block_pipe(void); void restore_pipe(int); int setsig(Trap *, sig_t, int); void setexecsig(Trap *, int); +#if HAVE_FLOCK || HAVE_LOCK_FCNTL +void mksh_lockfd(int); +void mksh_unlkfd(int); +#endif /* jobs.c */ void j_init(void); void j_exit(void); @@ -1680,6 +1828,7 @@ pid_t j_async(void); int j_stopped_running(void); /* lex.c */ int yylex(int); +void yyskiputf8bom(void); void yyerror(const char *, ...) MKSH_A_NORETURN MKSH_A_FORMAT(__printf__, 1, 2); @@ -1688,9 +1837,10 @@ void set_prompt(int, Source *); void pprompt(const char *, int); int promptlen(const char *); /* main.c */ -int include(const char *, int, const char **, int); +int include(const char *, int, const char **, bool); int command(const char *, int); -int shell(Source *volatile, int volatile); +int shell(Source * volatile, volatile bool); +/* argument MUST NOT be 0 */ void unwind(int) MKSH_A_NORETURN; void newenv(int); void quitenv(struct shf *); @@ -1706,9 +1856,9 @@ void warningf(bool, const char *, ...) MKSH_A_FORMAT(__printf__, 2, 3); void bi_errorf(const char *, ...) MKSH_A_FORMAT(__printf__, 1, 2); -#define errorfz() errorf("\1") -#define errorfxz(rc) errorfx((rc), "\1") -#define bi_errorfz() bi_errorf("\1") +#define errorfz() errorf(NULL) +#define errorfxz(rc) errorfx((rc), NULL) +#define bi_errorfz() bi_errorf(NULL) void internal_errorf(const char *, ...) MKSH_A_NORETURN MKSH_A_FORMAT(__printf__, 1, 2); @@ -1743,24 +1893,27 @@ struct tbl *ktenter(struct table *, const char *, uint32_t); void ktwalk(struct tstate *, struct table *); struct tbl *ktnext(struct tstate *); struct tbl **ktsort(struct table *); +#ifdef DF +void DF(const char *, ...) + MKSH_A_FORMAT(__printf__, 1, 2); +#endif /* misc.c */ void setctypes(const char *, int); void initctypes(void); size_t option(const char *); char *getoptions(void); -void change_flag(enum sh_flag, int, unsigned int); +void change_flag(enum sh_flag, int, bool); int parse_args(const char **, int, bool *); 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 *); 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 *); +void print_value_quoted(struct shf *, const char *); char *quote_value(const char *); -void print_columns(struct shf *, int, - char *(*)(char *, size_t, int, const void *), +void print_columns(struct shf *, unsigned int, + char *(*)(char *, size_t, unsigned int, const void *), const void *, size_t, size_t, bool); void strip_nuls(char *, int); ssize_t blocking_read(int, char *, size_t) @@ -1771,9 +1924,9 @@ char *do_realpath(const char *); void simplify_path(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 *); +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) +char *strdup_i(const char *, Area *); +char *strndup_i(const char *, size_t, Area *); #endif int unbksl(bool, int (*)(void), void (*)(int)); /* shf.c */ @@ -1789,12 +1942,12 @@ 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 +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) int shf_getc(struct shf *); int shf_putc(int, struct shf *); #else -#define shf_getc shf_getc_ -#define shf_putc shf_putc_ +#define shf_getc shf_getc_i +#define shf_putc shf_putc_i #endif int shf_putchar(int, struct shf *); ssize_t shf_puts(const char *, struct shf *); @@ -1812,7 +1965,8 @@ ssize_t shf_vfprintf(struct shf *, const char *, va_list) void initkeywords(void); struct op *compile(Source *, bool); bool parse_usec(const char *, struct timeval *); -char *yyrecursive(void); +char *yyrecursive(int); +void yyrecursive_pop(bool); /* tree.c */ void fptreef(struct shf *, int, const char *, ...); char *snptreef(char *, ssize_t, const char *, ...); @@ -1827,6 +1981,7 @@ void tfree(struct op *, Area *); void dumpchar(struct shf *, int); void dumptree(struct shf *, struct op *); void dumpwdvar(struct shf *, const char *); +void dumpioact(struct shf *shf, struct op *t); void vistree(char *, size_t, struct op *) MKSH_A_BOUNDED(__string__, 1, 2); void fpFUNCTf(struct shf *, int, bool, const char *, struct op *); @@ -1834,15 +1989,15 @@ void fpFUNCTf(struct shf *, int, bool, const char *, struct op *); void newblock(void); void popblock(void); void initvar(void); +struct block *varsearch(struct block *, struct tbl **, const char *, uint32_t); struct tbl *global(const char *); struct tbl *local(const char *, bool); 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); -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 setint_n(struct tbl *, mksh_ari_t, int); +struct tbl *typeset(const char *, uint32_t, uint32_t, int, int); void unset(struct tbl *, int); const char *skip_varname(const char *, int); const char *skip_wdvarname(const char *, bool); @@ -1855,6 +2010,7 @@ 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 *); +mksh_ari_t rndget(void); void rndset(long); enum Test_op { @@ -1904,7 +2060,7 @@ typedef struct test_env { int flags; /* TEF_* */ } Test_env; -extern const char *const dbtest_tokens[]; +extern const char * const dbtest_tokens[]; Test_op test_isop(Test_meta, const char *); int test_eval(Test_env *, Test_op, const char *, const char *, bool); @@ -1912,10 +2068,10 @@ int test_parse(Test_env *); 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 mksh_ttyst tty_state; /* saved tty state */ +EXTERN bool tty_hasstate; /* true if tty_state is valid */ -extern void tty_init(bool, bool); -extern void tty_close(void); +extern int tty_init_fd(void); /* initialise tty_fd, tty_devtty */ /* be sure not to interfere with anyone else's idea about EXTERN */ #ifdef EXTERN_DEFINED diff --git a/src/sh_flags.h b/src/sh_flags.h index a850220..1c8a30e 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.9 2011/06/12 15:37:10 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.12 2012/06/28 20:14:17 tg Exp $"); #define FN(sname,cname,ochar,flags) /* nothing */ #elif defined(SHFLAGS_ENUMS) #define FN(sname,cname,ochar,flags) cname, @@ -29,14 +29,18 @@ FN("bgnice", FBGNICE, 0, OF_ANY) /* ./. enable {} globbing (non-standard) */ FN("braceexpand", FBRACEEXPAND, 0, OF_ANY) +#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE) /* ./. Emacs command line editing mode */ FN("emacs", FEMACS, 0, OF_ANY) +#endif /* -e quit on error */ FN("errexit", FERREXIT, 'e', OF_ANY) +#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE) /* ./. Emacs command line editing mode, gmacs variant */ FN("gmacs", FGMACS, 0, OF_ANY) +#endif /* ./. reading EOF does not exit */ FN("ignoreeof", FIGNOREEOF, 0, OF_ANY) @@ -108,7 +112,7 @@ FN("utf8-mode", FUNICODE, 'U', OF_ANY) /* -v echo input */ FN("verbose", FVERBOSE, 'v', OF_ANY) -#if !MKSH_S_NOVI +#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE) /* ./. Vi command line editing mode */ FN("vi", FVI, 0, OF_ANY) diff --git a/src/shf.c b/src/shf.c index 7592e2b..82db720 100644 --- a/src/shf.c +++ b/src/shf.c @@ -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, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -24,7 +24,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.44 2011/09/07 15:24:20 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.56 2013/01/01 03:32:44 tg Exp $"); /* flags to shf_emptybuf() */ #define EB_READSW 0x01 /* about to switch to reading */ @@ -490,7 +490,7 @@ shf_getse(char *buf, ssize_t bsize, struct shf *shf) return (NULL); /* save room for NUL */ - --bsize; + --bsize; do { if (shf->rnleft == 0) { if (shf_fillbuf(shf) == EOF) @@ -552,7 +552,7 @@ shf_ungetc(int c, struct shf *shf) * Can unget what was read, but not something different; * we don't want to modify a string. */ - if (shf->rp[-1] != c) + if ((int)(shf->rp[-1]) != c) return (EOF); shf->flags &= ~SHF_EOF; shf->rp--; @@ -722,7 +722,7 @@ shf_snprintf(char *buf, ssize_t bsize, const char *fmt, ...) n = shf_vfprintf(&shf, fmt, args); va_end(args); /* NUL terminates */ - shf_sclose(&shf); + shf_sclose(&shf); return (n); } @@ -764,7 +764,12 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) ssize_t field, precision, len; unsigned long lnum; /* %#o produces the longest output */ - char numbuf[(8 * sizeof(long) + 2) / 3 + 1]; + char numbuf[(8 * sizeof(long) + 2) / 3 + 1 +#ifdef DEBUG + /* a NUL for LLVM/Clang scan-build */ + + 1 +#endif + ]; /* this stuff for dealing with the buffer */ ssize_t nwritten = 0; @@ -842,12 +847,16 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) continue; } if (ksh_isdigit(c)) { + bool overflowed = false; + tmp = c - '0'; - while (c = *fmt++, ksh_isdigit(c)) + while (c = *fmt++, ksh_isdigit(c)) { + if (notok2mul(2147483647, tmp, 10)) + overflowed = true; tmp = tmp * 10 + c - '0'; + } --fmt; - if (tmp < 0) - /* overflow? */ + if (overflowed) tmp = 0; if (flags & FL_DOT) precision = tmp; @@ -898,6 +907,16 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) integral: flags |= FL_NUMBER; cp = numbuf + sizeof(numbuf); +#ifdef DEBUG + /* + * this is necessary so Clang 3.2 realises + * utf_skipcols/shf_putc in the output loop + * terminate; these values are always ASCII + * so an out-of-bounds access cannot happen + * but Clang doesn't know that + */ + *--cp = '\0'; +#endif switch (c) { case 'd': @@ -949,6 +968,10 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) } } len = numbuf + sizeof(numbuf) - (s = cp); +#ifdef DEBUG + /* see above comment for Clang 3.2 */ + --len; +#endif if (flags & FL_DOT) { if (precision > len) { field = precision; @@ -967,14 +990,13 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) case 'c': flags &= ~FL_DOT; - numbuf[0] = (char)(VA(int)); - s = numbuf; - len = 1; - break; + c = (char)(VA(int)); + /* FALLTHROUGH */ case '%': default: numbuf[0] = c; + numbuf[1] = 0; s = numbuf; len = 1; break; @@ -1042,16 +1064,95 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args) return (shf_error(shf) ? EOF : nwritten); } -#ifdef MKSH_SMALL +#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) int shf_getc(struct shf *shf) { - return (shf_getc_(shf)); + return (shf_getc_i(shf)); } int shf_putc(int c, struct shf *shf) { - return (shf_putc_(c, shf)); + return (shf_putc_i(c, shf)); +} +#endif + +#ifdef DEBUG +const char * +cstrerror(int errnum) +{ +#undef strerror + return (strerror(errnum)); +#define strerror dontuse_strerror /* poisoned */ +} +#elif !HAVE_STRERROR + +#if HAVE_SYS_ERRLIST +#if !HAVE_SYS_ERRLIST_DECL +extern const int sys_nerr; +extern const char * const sys_errlist[]; +#endif +#endif + +const char * +cstrerror(int errnum) +{ + /* "Unknown error: " + sign + rough estimate + NUL */ + static char errbuf[15 + 1 + (8 * sizeof(int) + 2) / 3 + 1]; + +#if HAVE_SYS_ERRLIST + if (errnum > 0 && errnum < sys_nerr && sys_errlist[errnum]) + return (sys_errlist[errnum]); +#endif + + switch (errnum) { + case 0: + return ("Undefined error: 0"); +#ifdef EPERM + case EPERM: + return ("Operation not permitted"); +#endif +#ifdef ENOENT + case ENOENT: + return ("No such file or directory"); +#endif +#ifdef ESRCH + case ESRCH: + return ("No such process"); +#endif +#ifdef E2BIG + case E2BIG: + return ("Argument list too long"); +#endif +#ifdef ENOEXEC + case ENOEXEC: + return ("Exec format error"); +#endif +#ifdef ENOMEM + case ENOMEM: + return ("Cannot allocate memory"); +#endif +#ifdef EACCES + case EACCES: + return ("Permission denied"); +#endif +#ifdef ENOTDIR + case ENOTDIR: + return ("Not a directory"); +#endif +#ifdef EINVAL + case EINVAL: + return ("Invalid argument"); +#endif +#ifdef ELOOP + case ELOOP: + return ("Too many levels of symbolic links"); +#endif + default: + shf_snprintf(errbuf, sizeof(errbuf), + "Unknown error: %d", errnum); + return (errbuf); + } } #endif diff --git a/src/syn.c b/src/syn.c index b2a3f8b..bffee7a 100644 --- a/src/syn.c +++ b/src/syn.c @@ -1,7 +1,8 @@ /* $OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, + * 2011, 2012 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,16 +23,22 @@ #include "sh.h" -__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); +__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.88 2012/12/28 02:28:39 tg Exp $"); struct nesting_state { int start_token; /* token than began nesting (eg, FOR) */ int start_line; /* line nesting began on */ }; +struct yyrecursive_state { + struct yyrecursive_state *next; + struct ioword **old_herep; + int old_symbol; + int old_salias; + int old_nesting_type; + bool old_reject; +}; + static void yyparse(void); static struct op *pipeline(int); static struct op *andor(void); @@ -51,7 +58,7 @@ static struct op *newtp(int); static void syntaxerr(const char *) MKSH_A_NORETURN; static void nesting_push(struct nesting_state *, int); static void nesting_pop(struct nesting_state *); -static int assign_command(char *); +static int assign_command(const char *); static int inalias(struct source *); static Test_op dbtestp_isa(Test_env *, Test_meta); static const char *dbtestp_getopnd(Test_env *, Test_op, bool); @@ -64,6 +71,7 @@ static struct nesting_state nesting; /* \n changed to ; */ static bool reject; /* token(cf) gets symbol again */ static int symbol; /* yylex value */ +static int sALIAS = ALIAS; /* 0 in yyrecursive */ #define REJECT (reject = true) #define ACCEPT (reject = false) @@ -71,6 +79,9 @@ static int symbol; /* yylex value */ #define tpeek(cf) ((reject) ? (symbol) : (REJECT, symbol = yylex(cf))) #define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (/* CONSTCOND */ 0) +static const char Tcbrace[] = "}"; +static const char Tesac[] = "esac"; + static void yyparse(void) { @@ -227,18 +238,32 @@ nested(int type, int smark, int emark) nesting_push(&old_nesting, smark); t = c_list(true); - musthave(emark, KEYWORD|ALIAS); + musthave(emark, KEYWORD|sALIAS); nesting_pop(&old_nesting); return (block(type, t, NOBLOCK, NOWORDS)); } +static const char let_cmd[] = { + CHAR, 'l', CHAR, 'e', CHAR, 't', EOS +}; +static const char setA_cmd0[] = { + CHAR, 's', CHAR, 'e', CHAR, 't', EOS +}; +static const char setA_cmd1[] = { + CHAR, '-', CHAR, 'A', EOS +}; +static const char setA_cmd2[] = { + CHAR, '-', CHAR, '-', EOS +}; + static struct op * get_command(int cf) { struct op *t; - int c, iopn = 0, syniocf; + int c, iopn = 0, syniocf, lno; struct ioword *iop, **iops; XPtrV args, vars; + char *tcp; struct nesting_state old_nesting; /* NUFILE is small enough to leave this addition unchecked */ @@ -246,8 +271,8 @@ get_command(int cf) XPinit(args, 16); XPinit(vars, 16); - syniocf = KEYWORD|ALIAS; - switch (c = token(cf|KEYWORD|ALIAS|VARASN)) { + syniocf = KEYWORD|sALIAS; + switch (c = token(cf|KEYWORD|sALIAS|VARASN)) { default: REJECT; afree(iops, ATEMP); @@ -259,12 +284,12 @@ get_command(int cf) case LWORD: case REDIR: REJECT; - syniocf &= ~(KEYWORD|ALIAS); + syniocf &= ~(KEYWORD|sALIAS); t = newtp(TCOM); t->lineno = source->line; while (/* CONSTCOND */ 1) { cf = (t->u.evalflags ? ARRAYVAR : 0) | - (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD); + (XPsize(args) == 0 ? sALIAS|VARASN : CMDWORD); switch (tpeek(cf)) { case REDIR: while ((iop = synio(cf)) != NULL) { @@ -292,67 +317,50 @@ get_command(int cf) XPput(args, yylval.cp); break; - case '(': -#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); - if (XPsize(args) == 0 && XPsize(vars) == 0) { + case '(' /*)*/: + if (XPsize(args) == 0 && XPsize(vars) == 1 && + is_wdvarassign(yylval.cp)) { + /* wdarrassign: foo=(bar) */ ACCEPT; - goto Subshell; + + /* manipulate the vars string */ + tcp = XPptrv(vars)[(vars.len = 0)]; + /* 'varname=' -> 'varname' */ + tcp[wdscan(tcp, EOS) - tcp - 3] = EOS; + + /* construct new args strings */ + XPput(args, wdcopy(setA_cmd0, ATEMP)); + XPput(args, wdcopy(setA_cmd1, ATEMP)); + XPput(args, tcp); + XPput(args, wdcopy(setA_cmd2, ATEMP)); + + /* slurp in words till closing paren */ + while (token(CONTIN) == LWORD) + XPput(args, yylval.cp); + if (symbol != /*(*/ ')') + syntaxerr(NULL); + } else { + /* + * Check for "> foo (echo hi)" + * which AT&T ksh allows (not + * POSIX, but not disallowed) + */ + afree(t, ATEMP); + if (XPsize(args) == 0 && + XPsize(vars) == 0) { + ACCEPT; + goto Subshell; + } + + /* must be a function */ + if (iopn != 0 || XPsize(args) != 1 || + XPsize(vars) != 0) + syntaxerr(NULL); + ACCEPT; + musthave(/*(*/')', 0); + t = function_body(XPptrv(args)[0], false); } - - /* must be a function */ - if (iopn != 0 || XPsize(args) != 1 || - XPsize(vars) != 0) - syntaxerr(NULL); - ACCEPT; - musthave(/*(*/')', 0); - t = function_body(XPptrv(args)[0], false); goto Leave; -#ifndef MKSH_SMALL - is_wdarrassign: - { - static const char set_cmd0[] = { - CHAR, 's', CHAR, 'e', - CHAR, 't', EOS - }; - static const char set_cmd1[] = { - CHAR, '-', CHAR, 'A', EOS - }; - static const char set_cmd2[] = { - CHAR, '-', CHAR, '-', EOS - }; - char *tcp; - - 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)); - - /* slurp in words till closing paren */ - while (token(CONTIN) == LWORD) - XPput(args, yylval.cp); - if (symbol != /*(*/ ')') - syntaxerr(NULL); - - goto Leave; - } -#endif default: goto Leave; @@ -361,24 +369,21 @@ get_command(int cf) Leave: break; - case '(': + case '(': /*)*/ { + int subshell_nesting_type_saved; Subshell: - ++subshell_nesting_level; + subshell_nesting_type_saved = subshell_nesting_type; + subshell_nesting_type = ')'; t = nested(TPAREN, '(', ')'); - --subshell_nesting_level; + subshell_nesting_type = subshell_nesting_type_saved; break; + } case '{': /*}*/ t = nested(TBRACE, '{', '}'); break; - case MDPAREN: { - int lno; - static const char let_cmd[] = { - CHAR, 'l', CHAR, 'e', - CHAR, 't', EOS - }; - + case MDPAREN: /* leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ lno = source->line; ACCEPT; @@ -395,7 +400,6 @@ get_command(int cf) XPput(args, wdcopy(let_cmd, ATEMP)); XPput(args, yylval.cp); break; - } case DBRACKET: /* [[ .. ]] */ /* leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ @@ -452,12 +456,12 @@ get_command(int cf) t = newtp(TIF); t->left = c_list(true); t->right = thenpart(); - musthave(FI, KEYWORD|ALIAS); + musthave(FI, KEYWORD|sALIAS); nesting_pop(&old_nesting); break; case BANG: - syniocf &= ~(KEYWORD|ALIAS); + syniocf &= ~(KEYWORD|sALIAS); t = pipeline(0); if (t == NULL) syntaxerr(NULL); @@ -465,9 +469,9 @@ get_command(int cf) break; case TIME: - syniocf &= ~(KEYWORD|ALIAS); + syniocf &= ~(KEYWORD|sALIAS); t = pipeline(0); - if (t) { + if (t && t->type == TCOM) { t->str = alloc(2, ATEMP); /* TF_* flags */ t->str[0] = '\0'; @@ -516,7 +520,7 @@ dogroup(void) int c; struct op *list; - c = token(CONTIN|KEYWORD|ALIAS); + c = token(CONTIN|KEYWORD|sALIAS); /* * 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 @@ -530,7 +534,7 @@ dogroup(void) else syntaxerr(NULL); list = c_list(true); - musthave(c, KEYWORD|ALIAS); + musthave(c, KEYWORD|sALIAS); return (list); } @@ -539,7 +543,7 @@ thenpart(void) { struct op *t; - musthave(THEN, KEYWORD|ALIAS); + musthave(THEN, KEYWORD|sALIAS); t = newtp(0); t->left = c_list(true); if (t->left == NULL) @@ -553,7 +557,7 @@ elsepart(void) { struct op *t; - switch (token(KEYWORD|ALIAS|VARASN)) { + switch (token(KEYWORD|sALIAS|VARASN)) { case ELSE: if ((t = c_list(true)) == NULL) syntaxerr(NULL); @@ -577,7 +581,7 @@ caselist(void) struct op *t, *tl; int c; - c = token(CONTIN|KEYWORD|ALIAS); + c = token(CONTIN|KEYWORD|sALIAS); /* A {...} can be used instead of in...esac for case statements */ if (c == IN) c = ESAC; @@ -594,7 +598,7 @@ caselist(void) else tl->right = tc, tl = tc; } - musthave(c, KEYWORD|ALIAS); + musthave(c, KEYWORD|sALIAS); return (t); } @@ -610,7 +614,20 @@ casepart(int endtok) if (token(CONTIN | KEYWORD) != '(') REJECT; do { - musthave(LWORD, 0); + switch (token(0)) { + case LWORD: + break; + case '}': + case ESAC: + if (symbol != endtok) { + strdupx(yylval.cp, + symbol == '}' ? Tcbrace : Tesac, ATEMP); + break; + } + /* FALLTHROUGH */ + default: + syntaxerr(NULL); + } XPput(ptns, yylval.cp); } while (token(0) == '|'); REJECT; @@ -619,17 +636,23 @@ casepart(int endtok) musthave(')', 0); t->left = c_list(true); - /* Note: POSIX requires the ;; */ - if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) + + /* initialise to default for ;; or omitted */ + t->u.charflag = ';'; + /* SUSv4 requires the ;; except in the last casepart */ + if ((tpeek(CONTIN|KEYWORD|sALIAS)) != endtok) switch (symbol) { default: syntaxerr(NULL); - case BREAK: case BRKEV: + t->u.charflag = '|'; + if (0) + /* FALLTHROUGH */ case BRKFT: - t->u.charflag = - (symbol == BRKEV) ? '|' : - (symbol == BRKFT) ? '&' : ';'; + t->u.charflag = '&'; + /* FALLTHROUGH */ + case BREAK: + /* initialised above, but we need to eat the token */ ACCEPT; } return (t); @@ -642,7 +665,6 @@ function_body(char *name, { char *sname, *p; struct op *t; - bool old_func_parse; sname = wdstrip(name, 0); /*- @@ -663,14 +685,14 @@ function_body(char *name, * only accepts an open-brace. */ if (ksh_func) { - if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /*)*/) { - /* function foo () { */ + if (tpeek(CONTIN|KEYWORD|sALIAS) == '(' /*)*/) { + /* function foo () { //}*/ ACCEPT; musthave(')', 0); /* degrade to POSIX function */ ksh_func = false; } - musthave('{' /*}*/, CONTIN|KEYWORD|ALIAS); + musthave('{' /*}*/, CONTIN|KEYWORD|sALIAS); REJECT; } @@ -679,8 +701,6 @@ function_body(char *name, t->u.ksh_func = tobool(ksh_func); t->lineno = source->line; - old_func_parse = e->flags & EF_FUNC_PARSE; - e->flags |= EF_FUNC_PARSE; if ((t->left = get_command(CONTIN)) == NULL) { char *tv; /* @@ -701,8 +721,6 @@ function_body(char *name, t->left->vars[0] = NULL; t->left->lineno = 1; } - if (!old_func_parse) - e->flags &= ~EF_FUNC_PARSE; return (t); } @@ -715,7 +733,7 @@ wordlist(void) XPinit(args, 16); /* POSIX does not do alias expansion here... */ - if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) { + if ((c = token(CONTIN|KEYWORD|sALIAS)) != IN) { if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */ REJECT; @@ -750,7 +768,7 @@ block(int type, struct op *t1, struct op *t2, char **wp) return (t); } -const struct tokeninfo { +static const struct tokeninfo { const char *name; short val; short reserved; @@ -762,7 +780,7 @@ const struct tokeninfo { { "elif", ELIF, true }, { "fi", FI, true }, { "case", CASE, true }, - { "esac", ESAC, true }, + { Tesac, ESAC, true }, { "for", FOR, true }, { Tselect, SELECT, true }, { "while", WHILE, true }, @@ -773,7 +791,7 @@ const struct tokeninfo { { Tfunction, FUNCTION, true }, { "time", TIME, true }, { "{", '{', true }, - { "}", '}', true }, + { Tcbrace, '}', true }, { "!", BANG, true }, { "[[", DBRACKET, true }, /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */ @@ -796,7 +814,7 @@ initkeywords(void) struct tbl *p; ktinit(APERM, &keywords, - /* currently 28 keywords -> 80% of 64 (2^6) */ + /* currently 28 keywords: 75% of 64 = 2^6 */ 6); for (tt = tokentab; tt->name; tt++) { if (tt->reserved) { @@ -916,13 +934,13 @@ compile(Source *s, bool skiputf8bom) * $ */ static int -assign_command(char *s) +assign_command(const char *s) { if (!*s) return (0); return ((strcmp(s, Talias) == 0) || - (strcmp(s, "export") == 0) || - (strcmp(s, "readonly") == 0) || + (strcmp(s, Texport) == 0) || + (strcmp(s, Treadonly) == 0) || (strcmp(s, Ttypeset) == 0)); } @@ -948,13 +966,13 @@ static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS }; static const char dbtest_not[] = { CHAR, '!', EOS }; static const char dbtest_oparen[] = { CHAR, '(', EOS }; static const char dbtest_cparen[] = { CHAR, ')', EOS }; -const char *const dbtest_tokens[] = { +const char * const dbtest_tokens[] = { dbtest_or, dbtest_and, dbtest_not, dbtest_oparen, dbtest_cparen }; -const char db_close[] = { CHAR, ']', CHAR, ']', EOS }; -const char db_lthan[] = { CHAR, '<', EOS }; -const char db_gthan[] = { CHAR, '>', EOS }; +static const char db_close[] = { CHAR, ']', CHAR, ']', EOS }; +static const char db_lthan[] = { CHAR, '<', EOS }; +static const char db_gthan[] = { CHAR, '>', EOS }; /* * Test if the current token is a whatever. Accepts the current token if @@ -997,7 +1015,7 @@ dbtestp_isa(Test_env *te, Test_meta meta) db_close)) ? TO_NONNULL : TO_NONOP; if (ret != TO_NONOP) { ACCEPT; - if (meta < NELEM(dbtest_tokens)) + if ((unsigned int)meta < NELEM(dbtest_tokens)) save = wdcopy(dbtest_tokens[(int)meta], ATEMP); if (save) XPput(*te->pos.av, save); @@ -1106,32 +1124,65 @@ parse_usec(const char *s, struct timeval *tv) * a COMSUB recursively using the main shell parser and lexer */ char * -yyrecursive(void) +yyrecursive(int subtype MKSH_A_UNUSED) { struct op *t; char *cp; - bool old_reject; - int old_symbol; - struct ioword **old_herep; + struct yyrecursive_state *ys; + int stok, etok; + + if (subtype == FUNSUB) { + stok = '{'; + etok = '}'; + } else { + stok = '('; + etok = ')'; + } + + ys = alloc(sizeof(struct yyrecursive_state), ATEMP); /* tell the lexer to accept a closing parenthesis as EOD */ - ++subshell_nesting_level; + ys->old_nesting_type = subshell_nesting_type; + subshell_nesting_type = etok; /* push reject state, parse recursively, pop reject state */ - old_reject = reject; - old_symbol = symbol; + ys->old_reject = reject; + ys->old_symbol = symbol; ACCEPT; - old_herep = herep; + ys->old_herep = herep; + ys->old_salias = sALIAS; + sALIAS = 0; + ys->next = e->yyrecursive_statep; + e->yyrecursive_statep = ys; /* we use TPAREN as a helper container here */ - t = nested(TPAREN, '(', ')'); - herep = old_herep; - reject = old_reject; - symbol = old_symbol; + t = nested(TPAREN, stok, etok); + yyrecursive_pop(false); /* t->left because nested(TPAREN, ...) hides our goodies there */ cp = snptreef(NULL, 0, "%T", t->left); tfree(t, ATEMP); - --subshell_nesting_level; return (cp); } + +void +yyrecursive_pop(bool popall) +{ + struct yyrecursive_state *ys; + + popnext: + if (!(ys = e->yyrecursive_statep)) + return; + e->yyrecursive_statep = ys->next; + + sALIAS = ys->old_salias; + herep = ys->old_herep; + reject = ys->old_reject; + symbol = ys->old_symbol; + + subshell_nesting_type = ys->old_nesting_type; + + afree(ys, ATEMP); + if (popall) + goto popnext; +} diff --git a/src/tree.c b/src/tree.c index 9ade37b..8015a8d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,7 +1,8 @@ -/* $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $ */ +/* $OpenBSD: tree.c,v 1.20 2012/06/27 07:17:19 otto Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -22,7 +23,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.51 2011/09/07 15:24:21 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.67 2012/12/04 01:10:35 tg Exp $"); #define INDENT 8 @@ -36,6 +37,8 @@ static void iofree(struct ioword **, Area *); /* "foo& ; bar" and "foo |& ; bar" are invalid */ static bool prevent_semicolon; +static const char Telif_pT[] = "elif %T"; + /* * print a command tree */ @@ -52,6 +55,25 @@ ptree(struct op *t, int indent, struct shf *shf) return; switch (t->type) { case TCOM: + prevent_semicolon = false; + /* + * special-case 'var=<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) { + fptreef(shf, indent, "%S", t->vars[0]); + break; + } + if (t->vars) { w = (const char **)t->vars; while (*w) @@ -64,7 +86,6 @@ ptree(struct op *t, int indent, struct shf *shf) fptreef(shf, indent, "%S ", *w++); } else shf_puts("#no-args# ", shf); - prevent_semicolon = false; break; case TEXEC: t = t->left; @@ -126,38 +147,39 @@ ptree(struct op *t, int indent, struct shf *shf) } fptreef(shf, indent, "%Nesac "); break; -#ifndef MKSH_NO_DEPRECATED_WARNING +#ifdef DEBUG case TELIF: internal_errorf("TELIF in tree.c:ptree() unexpected"); /* FALLTHROUGH */ #endif case TIF: i = 2; + t1 = t; goto process_TIF; do { - t = t->right; + t1 = t1->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 + 5 - i, Telif_pT + i, t1->left); + t1 = t1->right; + if (t1->left != NULL) { fptreef(shf, indent, "%;"); fptreef(shf, indent + INDENT, "%s%N%T", - "then", t->left); + "then", t1->left); } - } while (t->right && t->right->type == TELIF); - if (t->right != NULL) { + } while (t1->right && t1->right->type == TELIF); + if (t1->right != NULL) { fptreef(shf, indent, "%;"); fptreef(shf, indent + INDENT, "%s%N%T", - "else", t->right); + "else", t1->right); } fptreef(shf, indent, "%;fi "); break; case TWHILE: case TUNTIL: - /* 6 == strlen("while"/"until") */ + /* 6 == strlen("while "/"until ") */ fptreef(shf, indent + 6, "%s %T", (t->type == TWHILE) ? "while" : "until", t->left); @@ -214,8 +236,10 @@ ptree(struct op *t, int indent, struct shf *shf) * often leads to an extra blank line, but it's not * worth worrying about) */ - if (need_nl) + if (need_nl) { shf_putc('\n', shf); + prevent_semicolon = true; + } } } @@ -256,8 +280,8 @@ pioact(struct shf *shf, int indent, struct ioword *iop) /* name/delim are NULL when printing syntax errors */ if (type == IOHERE) { if (iop->delim) - fptreef(shf, indent, "%S ", iop->delim); - else + wdvarput(shf, iop->delim, 0, WDS_TPUTS); + if (iop->flag & IOHERESTR) shf_putc(' ', shf); } else if (iop->name) fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ", @@ -270,6 +294,7 @@ static const char * wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) { int c; + const char *cs; /*- * problems: @@ -289,7 +314,7 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) case CHAR: c = *wp++; if ((opmode & WDS_MAGIC) && - (ISMAGIC(c) || c == '[' || c == NOT || + (ISMAGIC(c) || c == '[' || c == '!' || c == '-' || c == ']' || c == '*' || c == '?')) shf_putc(MAGIC, shf); shf_putc(c, shf); @@ -313,16 +338,20 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) } case COMSUB: shf_puts("$(", shf); + cs = ")"; + pSUB: while ((c = *wp++) != 0) shf_putc(c, shf); - shf_putc(')', shf); + shf_puts(cs, shf); break; + case FUNSUB: + shf_puts("${ ", shf); + cs = ";}"; + goto pSUB; case EXPRSUB: shf_puts("$((", shf); - while ((c = *wp++) != 0) - shf_putc(c, shf); - shf_puts("))", shf); - break; + cs = "))"; + goto pSUB; case OQUOTE: if (opmode & WDS_TPUTS) { quotelevel++; @@ -558,6 +587,7 @@ wdscan(const char *wp, int c) wp++; break; case COMSUB: + case FUNSUB: case EXPRSUB: while (*wp++ != 0) ; @@ -711,24 +741,42 @@ fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v) void vistree(char *dst, size_t sz, struct op *t) { - int c; + unsigned int c; char *cp, *buf; + size_t n; - buf = alloc(sz, ATEMP); - snptreef(buf, sz, "%T", t); + buf = alloc(sz + 8, ATEMP); + snptreef(buf, sz + 8, "%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; + vist_loop: + if (UTFMODE && (n = utf_mbtowc(&c, cp)) != (size_t)-1) { + if (c == 0 || n >= sz) + /* NUL or not enough free space */ + goto vist_out; + /* copy multibyte char */ + sz -= n; + while (n--) + *dst++ = *cp++; + goto vist_loop; } + if (--sz == 0 || (c = (unsigned char)(*cp++)) == 0) + /* NUL or not enough free space */ + goto vist_out; + if ((c & 0x60) == 0 || (c & 0x7F) == 0x7F) { + /* C0 or C1 control character or DEL */ + if (--sz == 0) + /* not enough free space for two chars */ + goto vist_out; + *dst++ = (c & 0x80) ? '$' : '^'; + c = (c & 0x7F) ^ 0x40; + } else if (UTFMODE && c > 0x7F) { + /* better not try to display broken multibyte chars */ + c = '?'; + } + *dst++ = c; + goto vist_loop; + + vist_out: *dst = '\0'; afree(buf, ATEMP); } @@ -747,7 +795,7 @@ dumpchar(struct shf *shf, int c) /* see: wdvarput */ static const char * -dumpwdvar_(struct shf *shf, const char *wp, int quotelevel) +dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel) { int c; @@ -779,6 +827,9 @@ dumpwdvar_(struct shf *shf, const char *wp, int quotelevel) closeandout: shf_putc('>', shf); break; + case FUNSUB: + shf_puts("FUNSUB<", shf); + goto dumpsub; case EXPRSUB: shf_puts("EXPRSUB<", shf); goto dumpsub; @@ -799,7 +850,7 @@ dumpwdvar_(struct shf *shf, const char *wp, int quotelevel) while ((c = *wp++) != 0) dumpchar(shf, c); shf_putc('|', shf); - wp = dumpwdvar_(shf, wp, 0); + wp = dumpwdvar_i(shf, wp, 0); break; case CSUBST: shf_puts("]CSUBST(", shf); @@ -826,16 +877,75 @@ dumpwdvar_(struct shf *shf, const char *wp, int quotelevel) void dumpwdvar(struct shf *shf, const char *wp) { - dumpwdvar_(shf, wp, 0); + dumpwdvar_i(shf, wp, 0); +} + +void +dumpioact(struct shf *shf, struct op *t) +{ + struct ioword **ioact, *iop; + + if ((ioact = t->ioact) == NULL) + return; + + shf_puts("{IOACT", shf); + while ((iop = *ioact++) != NULL) { + int type = iop->flag & IOTYPE; +#define DT(x) case x: shf_puts(#x, shf); break; +#define DB(x) if (iop->flag & x) shf_puts("|" #x, shf); + + shf_putc(';', shf); + switch (type) { + DT(IOREAD) + DT(IOWRITE) + DT(IORDWR) + DT(IOHERE) + DT(IOCAT) + DT(IODUP) + default: + shf_fprintf(shf, "unk%d", type); + } + DB(IOEVAL) + DB(IOSKIP) + DB(IOCLOB) + DB(IORDUP) + DB(IONAMEXP) + DB(IOBASH) + DB(IOHERESTR) + DB(IONDELIM) + shf_fprintf(shf, ",unit=%d", iop->unit); + if (iop->delim) { + shf_puts(",delim<", shf); + dumpwdvar(shf, iop->delim); + shf_putc('>', shf); + } + if (iop->name) { + if (iop->flag & IONAMEXP) { + shf_puts(",name=", shf); + print_value_quoted(shf, iop->name); + } else { + shf_puts(",name<", shf); + dumpwdvar(shf, iop->name); + shf_putc('>', shf); + } + } + if (iop->heredoc) { + shf_puts(",heredoc=", shf); + print_value_quoted(shf, iop->heredoc); + } +#undef DT +#undef DB + } + shf_putc('}', shf); } void dumptree(struct shf *shf, struct op *t) { - int i; + int i, j; const char **w, *name; struct op *t1; - static int nesting = 0; + static int nesting; for (i = 0; i < nesting; ++i) shf_putc('\t', shf); @@ -845,6 +955,7 @@ dumptree(struct shf *shf, struct op *t) name = "(null)"; goto out; } + dumpioact(shf, t); switch (t->type) { #define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/ @@ -854,7 +965,7 @@ dumptree(struct shf *shf, struct op *t) w = (const char **)t->vars; while (*w) { shf_putc('\n', shf); - for (int j = 0; j < nesting; ++j) + for (j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " var%d<", i++); dumpwdvar(shf, *w++); @@ -867,7 +978,7 @@ dumptree(struct shf *shf, struct op *t) w = t->args; while (*w) { shf_putc('\n', shf); - for (int j = 0; j < nesting; ++j) + for (j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " arg%d<", i++); dumpwdvar(shf, *w++); @@ -907,7 +1018,7 @@ dumptree(struct shf *shf, struct op *t) w = t->args; while (*w) { shf_putc('\n', shf); - for (int j = 0; j < nesting; ++j) + for (j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " arg%d<", i++); dumpwdvar(shf, *w++); @@ -922,7 +1033,7 @@ dumptree(struct shf *shf, struct op *t) w = (const char **)t->vars; while (*w) { shf_putc('\n', shf); - for (int j = 0; j < nesting; ++j) + for (j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " var%d<", i++); dumpwdvar(shf, *w++); @@ -937,7 +1048,7 @@ dumptree(struct shf *shf, struct op *t) i = 0; for (t1 = t->left; t1 != NULL; t1 = t1->right) { shf_putc('\n', shf); - for (int j = 0; j < nesting; ++j) + for (j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " sub%d[(", i); w = (const char **)t1->vars; @@ -948,6 +1059,7 @@ dumptree(struct shf *shf, struct op *t) ++w; } shf_putc(')', shf); + dumpioact(shf, t); shf_putc('\n', shf); dumptree(shf, t1->left); shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++); @@ -974,6 +1086,7 @@ dumptree(struct shf *shf, struct op *t) shf_putc('\n', shf); dumptree(shf, t->left); t = t->right; + dumpioact(shf, t); if (t->left != NULL) { shf_puts(" /TTHEN:\n", shf); dumptree(shf, t->left); @@ -981,6 +1094,7 @@ dumptree(struct shf *shf, struct op *t) if (t->right && t->right->type == TELIF) { shf_puts(" /TELIF:", shf); t = t->right; + dumpioact(shf, t); goto dumpif; } if (t->right != NULL) { diff --git a/src/var.c b/src/var.c index 315294e..19527e0 100644 --- a/src/var.c +++ b/src/var.c @@ -1,7 +1,8 @@ /* $OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $ */ /*- - * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + * 2011, 2012, 2013 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices @@ -26,7 +27,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/var.c,v 1.132 2011/09/07 15:24:22 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var.c,v 1.166 2013/02/18 22:24:52 tg Exp $"); /*- * Variables @@ -38,7 +39,6 @@ __RCSID("$MirOS: src/bin/mksh/var.c,v 1.132 2011/09/07 15:24:22 tg Exp $"); * 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; @@ -130,8 +130,8 @@ initvar(void) struct tbl *tp; ktinit(APERM, &specials, - /* currently 12 specials -> 80% of 16 (2^4) */ - 4); + /* currently 14 specials: 75% of 32 = 2^5 */ + 5); while (i < V_MAX - 1) { tp = ktenter(&specials, initvar_names[i], hash(initvar_names[i])); @@ -140,8 +140,8 @@ initvar(void) } } -/* common code for several functions below */ -static struct block * +/* common code for several functions below and c_typeset() */ +struct block * varsearch(struct block *l, struct tbl **vpp, const char *vn, uint32_t h) { register struct tbl *vp; @@ -260,7 +260,7 @@ global(const char *n) vp->flag &= ~(ISSET|INTEGER); break; case '?': - vp->val.i = exstat; + vp->val.i = exstat & 0xFF; break; case '#': vp->val.i = l->argc; @@ -299,6 +299,7 @@ local(const char *n, bool copy) /* check to see if this is an array */ n = array_index_calc(n, &array, &val); + mkssert(n != NULL); h = hash(n); if (!ksh_isalphx(*n)) { vp = &vtemp; @@ -405,11 +406,11 @@ int setstr(struct tbl *vq, const char *s, int error_ok) { char *salloc = NULL; - int no_ro_check = error_ok & 0x4; + bool no_ro_check = tobool(error_ok & 0x4); error_ok &= ~0x4; if ((vq->flag & RDONLY) && !no_ro_check) { - warningf(true, "%s: %s", vq->name, "is read only"); + warningf(true, "read-only: %s", vq->name); if (!error_ok) errorfxz(2); return (0); @@ -417,12 +418,15 @@ setstr(struct tbl *vq, const char *s, int error_ok) if (!(vq->flag&INTEGER)) { /* string dest */ if ((vq->flag&ALLOC)) { +#ifndef MKSH_SMALL /* debugging */ if (s >= vq->val.s && - s <= vq->val.s + strlen(vq->val.s)) + s <= vq->val.s + strlen(vq->val.s)) { internal_errorf( "setstr: %s=%s: assigning to self", vq->name, s); + } +#endif afree(vq->val.s, vq->areap); } vq->flag &= ~(ISSET|ALLOC); @@ -469,10 +473,10 @@ setint(struct tbl *vq, mksh_ari_t n) static int getint(struct tbl *vp, mksh_ari_t *nump, bool arith) { - char *s; int c, base, neg; + mksh_uari_t num; + const char *s; bool have_base = false; - mksh_ari_t num; if (vp->flag&SPECIAL) getspec(vp); @@ -487,31 +491,32 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) base = 10; num = 0; neg = 0; - if (arith && *s == '0' && *(s+1)) { - s++; - if (*s == 'x' || *s == 'X') { - s++; - base = 16; - } else if (vp->flag & ZEROFIL) { - while (*s == '0') - s++; - } else - base = 8; + if (arith && s[0] == '0' && (s[1] | 0x20) == 'x') { + s += 2; + base = 16; have_base = true; } - for (c = *s++; c ; c = *s++) { +#ifdef MKSH_LEGACY_MODE + if (arith && s[0] == '0' && ksh_isdigit(s[1]) && + !(vp->flag & ZEROFIL)) { + /* interpret as octal (deprecated) */ + base = 8; + have_base = true; + } +#endif + while ((c = *s++)) { if (c == '-') { neg++; continue; } else if (c == '#') { - base = (int)num; - if (have_base || base < 1 || base > 36) + if (have_base || num < 1 || num > 36) return (-1); + base = (int)num; if (base == 1) { unsigned int wc; if (!UTFMODE) - wc = *(unsigned char *)s; + wc = *(const unsigned char *)s; else if (utf_mbtowc(&wc, s) == (size_t)-1) /* OPTU-8 -> OPTU-16 */ /* @@ -519,7 +524,7 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) * the same as 1#\x80 does, thus is * not round-tripping correctly XXX) */ - wc = 0xEF00 + *(unsigned char *)s; + wc = 0xEF00 + *(const unsigned char *)s; *nump = (mksh_ari_t)wc; return (1); } @@ -538,9 +543,7 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) return (-1); num = num * base + c; } - if (neg) - num = -num; - *nump = num; + *nump = neg ? -((mksh_ari_t)num) : (mksh_ari_t)num; return (base); } @@ -556,7 +559,7 @@ setint_v(struct tbl *vq, struct tbl *vp, bool arith) if ((base = getint(vp, &num, arith)) == -1) return (NULL); - setint_n(vq, num); + setint_n(vq, num, 0); if (vq->type == 0) /* default base */ vq->type = base; @@ -565,7 +568,7 @@ setint_v(struct tbl *vq, struct tbl *vp, bool arith) /* convert variable vq to integer variable, setting its value to num */ void -setint_n(struct tbl *vq, mksh_ari_t num) +setint_n(struct tbl *vq, mksh_ari_t num, int newbase) { if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) { vq->flag &= ~ALLOC; @@ -573,6 +576,8 @@ setint_n(struct tbl *vq, mksh_ari_t num) afree(vq->val.s, vq->areap); } vq->val.i = num; + if (newbase != 0) + vq->type = newbase; vq->flag |= ISSET|INTEGER; if (vq->flag&SPECIAL) setspec(vq); @@ -611,10 +616,13 @@ formatstr(struct tbl *vp, const char *s) --slen; } if (vp->flag & ZEROFIL && vp->flag & INTEGER) { + if (!s[0] || !s[1]) + goto uhm_no; if (s[1] == '#') n = 2; else if (s[2] == '#') n = 3; + uhm_no: if (vp->u2.field <= n) n = 0; } @@ -665,6 +673,8 @@ exportprep(struct tbl *vp, const char *val) char *op = (vp->flag&ALLOC) ? vp->val.s : NULL; size_t namelen, vallen; + mkssert(val != NULL); + namelen = strlen(vp->name); vallen = strlen(val) + 1; @@ -698,10 +708,6 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) /* check for valid variable name, search for value */ val = skip_varname(var, false); - if (val == var) - return (NULL); - mkssert(var != NULL); - mkssert(*var != 0); if (*val == '[') { if (set_refflag != SRF_NOP) errorf("%s: %s", var, @@ -713,7 +719,7 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) * 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 + * substitution is performed on the [expression] which * would be a major security hole. */ if (set & IMPORT) { @@ -731,10 +737,16 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) ++val; vappend = true; } + } else if ((val[0] != '\0') || (set & IMPORT)) { + /* + * must have a = when setting a variable by importing + * the original environment, otherwise be empty; we + * also end up here when a variable name was invalid + * or none given + */ + return (NULL); } else { - /* importing from original environment: must have an = */ - if (set & IMPORT) - return (NULL); + /* just varname with no value part nor equals sign */ strdupx(tvar, var, ATEMP); val = NULL; /* handle foo[*] => foo (whole array) mapping for R39b */ @@ -802,7 +814,7 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) if ((vpbase->flag&RDONLY) && (val || clr || (set & ~EXPORT))) /* XXX check calls - is error here ok by POSIX? */ - errorfx(2, "%s: %s", tvar, "is read only"); + errorfx(2, "read-only: %s", tvar); afree(tvar, ATEMP); /* most calls are with set/clr == 0 */ @@ -1084,38 +1096,9 @@ getspec(struct tbl *vp) { register mksh_ari_t i; int st; + struct timeval tv; switch ((st = special(vp->name))) { - case V_SECONDS: - /* - * On start up the value of SECONDS is used before - * it has been set - don't do anything in this case - * (see initcoms[] in main.c). - */ - if (vp->flag & ISSET) { - struct timeval tv; - - gettimeofday(&tv, NULL); - i = tv.tv_sec - seconds; - } else - return; - break; - case V_RANDOM: - /* - * this is the same Linear Congruential PRNG as Borland - * C/C++ allegedly uses in its built-in rand() function - */ - i = ((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF; - break; - case V_HISTSIZE: - i = histsize; - break; - case V_OPTIND: - i = user_opt.uoptind; - break; - case V_LINENO: - i = current_lineno + user_lineno; - break; case V_COLUMNS: case V_LINES: /* @@ -1125,15 +1108,62 @@ getspec(struct tbl *vp) * and the window is then resized, the app won't * see the change cause the environ doesn't change. */ - change_winsz(); - i = st == V_COLUMNS ? x_cols : x_lins; + if (got_winch) + change_winsz(); + break; + } + switch (st) { + case V_BASHPID: + i = (mksh_ari_t)procpid; + break; + case V_COLUMNS: + i = x_cols; + break; + case V_HISTSIZE: + i = histsize; + break; + case V_LINENO: + i = current_lineno + user_lineno; + break; + case V_LINES: + i = x_lins; + break; + case V_EPOCHREALTIME: { + /* 10(%u) + 1(.) + 6 + NUL */ + char buf[18]; + + vp->flag &= ~SPECIAL; + mksh_TIME(tv); + shf_snprintf(buf, sizeof(buf), "%u.%06u", + (unsigned)tv.tv_sec, (unsigned)tv.tv_usec); + setstr(vp, buf, KSH_RETURN_ERROR | 0x4); + vp->flag |= SPECIAL; + return; + } + case V_OPTIND: + i = user_opt.uoptind; + break; + case V_RANDOM: + i = rndget(); + break; + case V_SECONDS: + /* + * On start up the value of SECONDS is used before + * it has been set - don't do anything in this case + * (see initcoms[] in main.c). + */ + if (vp->flag & ISSET) { + mksh_TIME(tv); + i = tv.tv_sec - seconds; + } else + return; break; default: /* do nothing, do not touch vp at all */ return; } vp->flag &= ~SPECIAL; - setint_n(vp, i); + setint_n(vp, i, 0); vp->flag |= SPECIAL; } @@ -1145,6 +1175,15 @@ setspec(struct tbl *vp) int st; switch ((st = special(vp->name))) { +#if HAVE_PERSISTENT_HISTORY + case V_HISTFILE: + sethistfile(str_val(vp)); + return; +#endif + case V_IFS: + setctypes(s = str_val(vp), C_IFS); + ifs0 = *s; + return; case V_PATH: if (path) afree(path, APERM); @@ -1153,10 +1192,6 @@ setspec(struct tbl *vp) /* clear tracked aliases */ flushcom(true); return; - case V_IFS: - setctypes(s = str_val(vp), C_IFS); - ifs0 = *s; - return; case V_TMPDIR: if (tmpdir) { afree(tmpdir, APERM); @@ -1176,20 +1211,21 @@ setspec(struct tbl *vp) strdupx(tmpdir, s, APERM); } return; -#if HAVE_PERSISTENT_HISTORY - case V_HISTFILE: - sethistfile(str_val(vp)); - return; -#endif - /* common sub-cases */ - case V_OPTIND: - case V_HISTSIZE: case V_COLUMNS: case V_LINES: + if (vp->flag & IMPORT) { + /* do not touch */ + unspecial(vp->name); + vp->flag &= ~SPECIAL; + return; + } + /* FALLTHROUGH */ + case V_HISTSIZE: + case V_LINENO: + case V_OPTIND: case V_RANDOM: case V_SECONDS: - case V_LINENO: case V_TMOUT: vp->flag &= ~SPECIAL; if (getint(vp, &i, false) == -1) { @@ -1208,20 +1244,24 @@ setspec(struct tbl *vp) /* process the singular parts of the common cases */ switch (st) { - case V_OPTIND: - getopts_reset((int)i); - break; - case V_HISTSIZE: - sethistsize((int)i); - break; case V_COLUMNS: if (i >= MIN_COLS) x_cols = i; break; + case V_HISTSIZE: + sethistsize(i); + break; + case V_LINENO: + /* The -1 is because line numbering starts at 1. */ + user_lineno = (unsigned int)i - current_lineno - 1; + break; case V_LINES: if (i >= MIN_LINS) x_lins = i; break; + case V_OPTIND: + getopts_reset((int)i); + break; case V_RANDOM: /* * mksh R39d+ no longer has the traditional repeatability @@ -1233,14 +1273,10 @@ setspec(struct tbl *vp) { struct timeval tv; - gettimeofday(&tv, NULL); + mksh_TIME(tv); seconds = tv.tv_sec - i; } break; - case V_LINENO: - /* 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; @@ -1250,7 +1286,19 @@ setspec(struct tbl *vp) static void unsetspec(struct tbl *vp) { + /* + * AT&T ksh man page says OPTIND, OPTARG and _ lose special + * meaning, but OPTARG does not (still set by getopts) and _ is + * also still set in various places. Don't know what AT&T does + * for HISTSIZE, HISTFILE. Unsetting these in AT&T ksh does not + * loose the 'specialness': IFS, COLUMNS, PATH, TMPDIR + */ + switch (special(vp->name)) { + case V_IFS: + setctypes(TC_IFSWS, C_IFS); + ifs0 = ' '; + break; case V_PATH: if (path) afree(path, APERM); @@ -1258,10 +1306,6 @@ unsetspec(struct tbl *vp) /* clear tracked aliases */ flushcom(true); break; - case V_IFS: - setctypes(" \t\n", C_IFS); - ifs0 = ' '; - break; case V_TMPDIR: /* should not become unspecial */ if (tmpdir) { @@ -1276,14 +1320,6 @@ unsetspec(struct tbl *vp) /* AT&T ksh leaves previous value in place */ unspecial(vp->name); break; - - /* - * AT&T ksh man page says OPTIND, OPTARG and _ lose special - * meaning, but OPTARG does not (still set by getopts) and _ is - * also still set in various places. Don't know what AT&T does - * for HISTSIZE, HISTFILE. Unsetting these in AT&T ksh does not - * loose the 'specialness': IFS, COLUMNS, PATH, TMPDIR - */ } } @@ -1377,39 +1413,35 @@ set_array(const char *var, bool reset, const char **vals) { struct tbl *vp, *vq; mksh_uari_t i = 0, j = 0; - const char *ccp; -#ifndef MKSH_SMALL + const char *ccp = var; char *cp = NULL; size_t n; -#endif - /* to get local array, use "typeset foo; set -A foo" */ -#ifndef MKSH_SMALL + /* to get local array, use "local foo; set -A foo" */ n = strlen(var); if (n > 0 && var[n - 1] == '+') { /* append mode */ reset = false; strndupx(cp, var, n - 1, ATEMP); + ccp = cp; } -#define CPORVAR (cp ? cp : var) -#else -#define CPORVAR var -#endif - vp = global(CPORVAR); + vp = global(ccp); /* Note: AT&T ksh allows set -A but not set +A of a read-only var */ if ((vp->flag&RDONLY)) - errorfx(2, "%s: %s", CPORVAR, "is read only"); + errorfx(2, "read-only: %s", ccp); /* This code is quite non-optimal */ - if (reset) + if (reset) { /* trash existing values and attributes */ unset(vp, 1); + /* allocate-by-access the [0] element to keep in scope */ + arraysearch(vp, 0); + } /* * TODO: would be nice for assignment to completely succeed or * completely fail. Only really effects integer arrays: * evaluation of some of vals[] may fail... */ -#ifndef MKSH_SMALL if (cp != NULL) { /* find out where to set when appending */ for (vq = vp; vq; vq = vq->u.array) { @@ -1420,9 +1452,7 @@ set_array(const char *var, bool reset, const char **vals) } afree(cp, ATEMP); } -#endif while ((ccp = vals[i])) { -#ifndef MKSH_SMALL if (*ccp == '[') { int level = 0; @@ -1443,7 +1473,6 @@ set_array(const char *var, bool reset, const char **vals) } else ccp = vals[i]; } -#endif vq = arraysearch(vp, j); /* would be nice to deal with errors here... (see above) */ @@ -1458,19 +1487,9 @@ set_array(const char *var, bool reset, const char **vals) void change_winsz(void) { - if (x_lins < 0) { - /* first time initialisation */ -#ifdef TIOCGWINSZ - if (tty_fd < 0) - /* non-FTALKING, try to get an fd anyway */ - tty_init(true, false); -#endif - x_cols = -1; - } - #ifdef TIOCGWINSZ /* check if window size has changed */ - if (tty_fd >= 0) { + if (tty_init_fd() < 2) { struct winsize ws; if (ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) { @@ -1500,10 +1519,20 @@ hash(const void *s) NZATInit(h); NZATUpdateString(h, s); - NZATFinish(h); + NZAATFinish(h); return (h); } +mksh_ari_t +rndget(void) +{ + /* + * this is the same Linear Congruential PRNG as Borland + * C/C++ allegedly uses in its built-in rand() function + */ + return (((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF); +} + void rndset(long v) { diff --git a/src/var_spec.h b/src/var_spec.h index b3bef4b..1ed25e2 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.2 2011/06/05 19:58:21 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.6 2012/11/30 16:45:25 tg Exp $"); #define FN(name) /* nothing */ #elif defined(VARSPEC_ENUMS) #define FN(name) V_##name, @@ -19,7 +19,9 @@ __RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.2 2011/06/05 19:58:21 tg Exp $"); F0(NONE) /* 1 and up are special variables */ +FN(BASHPID) FN(COLUMNS) +FN(EPOCHREALTIME) #if HAVE_PERSISTENT_HISTORY FN(HISTFILE) #endif