From 2cfc3e5c3d237db7a2994b930199834893aa11cf Mon Sep 17 00:00:00 2001 From: tg Date: Sun, 29 May 2011 02:18:57 +0000 Subject: [PATCH] mksh R40 Release Candidate 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testsuite: • add new need-pass: {yes|no} attribute, default yes • exit with 1 if a need-pass test failed unexpectedly idea by Kacper Kornet • mark utf8bom-2 as need-pass: no Infrstructure: • add housekeeping function for making a tty raw • switch functions with unused results to void • struct op: u.charflag contains last char of ;; in TPAT • var.c:arraysearch is now a global function Language: • add ;& (fall through) and ;| (examine next) delimiters in addition to ;; (end case) as zsh extensions, because POSIX standardised on ;& already • add -A (read into array), -N (read exactly n bytes), -n (read up to n bytes), -t (timeout) flags for read from ksh93 • allow read -N -1 or -n -1 to slurp the entire input • add -a (read into array the input characters) extension specific to mksh to read, idea by David Korn • add -e (exit with error if PWD was not set correctly after a physical cd) to cd builtin, mandated by next POSIX, and change error codes accordingly Rewrites: • full rewrite of read builtin and its manpage section • add regression tetss for most of the new functionality • duplicate hexdump demo tests for use of read -a • use read -raN-1 in dot.mkshrc to get NUL safe base64, DJB cdb hash and Jenkins one-at-a-time hash functions --- check.pl | 34 +++- check.t | 316 ++++++++++++++++++++++++++++++++-- dot.mkshrc | 50 +++--- edit.c | 73 ++++---- exec.c | 33 +++- funcs.c | 487 ++++++++++++++++++++++++++++++++++++----------------- lex.c | 6 +- misc.c | 43 +++-- mksh.1 | 188 +++++++++++++++------ sh.h | 18 +- syn.c | 20 ++- tree.c | 9 +- var.c | 13 +- 13 files changed, 971 insertions(+), 319 deletions(-) diff --git a/check.pl b/check.pl index ef4b52a..7dfab36 100644 --- a/check.pl +++ b/check.pl @@ -1,4 +1,4 @@ -# $MirOS: src/bin/mksh/check.pl,v 1.26 2011/03/28 21:58:05 tg Exp $ +# $MirOS: src/bin/mksh/check.pl,v 1.27 2011/05/29 02:18:47 tg Exp $ # $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $ #- # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011 @@ -197,6 +197,7 @@ EOF 'expected-stderr-pattern', 'm', 'category', 'm', 'need-ctty', '', + 'need-pass', '', ); # Filled in by read_test() %internal_test_fields = ( @@ -217,6 +218,7 @@ $tempe = "/tmp/rte$$"; $tempdir = "/tmp/rtd$$"; $nfailed = 0; +$nifailed = 0; $nxfailed = 0; $npassed = 0; $nxpassed = 0; @@ -304,12 +306,13 @@ if (-d $test_set) { } &cleanup_exit() if !defined $ret; -$tot_failed = $nfailed + $nxfailed; +$tot_failed = $nfailed + $nifailed + $nxfailed; $tot_passed = $npassed + $nxpassed; if ($tot_failed || $tot_passed) { print "Total failed: $tot_failed"; + print " ($nifailed ignored)" if $nifailed; print " ($nxfailed unexpected)" if $nxfailed; - print " (as expected)" if $nfailed && !$nxfailed; + print " (as expected)" if $nfailed && !$nxfailed && !$nifailed; print "\nTotal passed: $tot_passed"; print " ($nxpassed unexpected)" if $nxpassed; print "\n"; @@ -323,7 +326,11 @@ cleanup_exit local($sig, $exitcode) = ('', 1); if ($_[0] eq 'ok') { - $exitcode = 0; + unless ($nxfailed) { + $exitcode = 0; + } else { + $exitcode = 1; + } } elsif ($_[0] ne '') { $sig = $_[0]; } @@ -620,8 +627,13 @@ run_test if ($failed) { if (!$test{'expected-fail'}) { - print "FAIL $name\n"; - $nxfailed++; + if ($test{'need-pass'}) { + print "FAIL $name\n"; + $nxfailed++; + } else { + print "FAIL $name (ignored)\n"; + $nifailed++; + } } else { print "fail $name (as expected)\n"; $nfailed++; @@ -1079,6 +1091,16 @@ read_test } else { $test{'need-ctty'} = 0; } + if (defined $test{'need-pass'}) { + if ($test{'need-pass'} !~ /^(yes|no)$/) { + print STDERR + "$prog:$test{':long-name'}: bad value for need-pass field\n"; + return undef; + } + $test{'need-pass'} = $1 eq 'yes'; + } else { + $test{'need-pass'} = 1; + } if (defined $test{'arguments'}) { local($firstc) = substr($test{'arguments'}, 0, 1); diff --git a/check.t b/check.t index 8f75620..bfd80a8 100644 --- a/check.t +++ b/check.t @@ -1,4 +1,4 @@ -# $MirOS: src/bin/mksh/check.t,v 1.454 2011/05/07 02:02:45 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.455 2011/05/29 02:18:47 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 $ @@ -25,7 +25,7 @@ # http://www.research.att.com/~gsf/public/ifs.sh expected-stdout: - @(#)MIRBSD KSH R39 2011/05/06 + @(#)MIRBSD KSH R39 2011/05/28 description: Check version of shell. stdin: @@ -1005,6 +1005,43 @@ expected-stdout: 1 /bin 0 /tmp --- +name: cd-pe +description: + Check package for cd -Pe +need-pass: no +file-setup: file 644 "x" + mkdir noread noread/target noread/target/subdir + ln -s noread link + chmod 311 noread + cd -P$1 . + echo 0=$? + bwd=$PWD + cd -P$1 link/target + echo 1=$?,${PWD#$bwd/} + epwd=$($TSHELL -c pwd 2>/dev/null) + echo pwd=$?,$epwd + mv ../../noread ../../renamed + cd -P$1 subdir + echo 2=$?,${PWD#$bwd/} + cd $bwd + chmod 755 renamed + rm -rf noread link renamed +stdin: + export TSHELL="$__progname" + "$__progname" x + echo "now with -e:" + "$__progname" x e +expected-stdout: + 0=0 + 1=0,noread/target + pwd=1, + 2=0,noread/target/subdir + now with -e: + 0=0 + 1=0,noread/target + pwd=1, + 2=1,noread/target/subdir +--- name: env-prompt description: Check that prompt not printed when processing ENV @@ -3764,6 +3801,34 @@ expected-stdout: --- +name: read-ext-1 +description: + Check read with number of bytes specified, and -A +stdin: + print 'foo\nbar' >x1 + print -n x >x2 + print 'foo\\ bar baz' >x3 + x1a=u; read x1a " + print -r "x1b=<$x1b>" + print -r "x2a=$r2a<$x2a>" + print -r "x2b=$r2b<$x2b>" + print -r "x2c=$r2c<$x2c>" + print -r "x3a=<${x3a[0]}|${x3a[1]}|${x3a[2]}>" +expected-stdout: + x1a= + x1b= + x2a=1 + x2b=1 + x2c=0 + x3a= +--- name: regression-1 description: Lex array code had problems with this. @@ -5722,6 +5787,7 @@ description: XXX if the OS can already execute them, we lose note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text +need-pass: no category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh env-setup: !FOO=BAR! stdin: @@ -6822,7 +6888,7 @@ expected-stderr-pattern: /1#: unexpected ''/ expected-exit: e != 0 --- -name: integer-base-one-3A +name: integer-base-one-3As description: some sample code for hexdumping stdin: @@ -6889,7 +6955,7 @@ expected-stdout: 00000110 EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE |................| 00000120 FF 0A - |..| --- -name: integer-base-one-3W +name: integer-base-one-3Ws description: some sample code for hexdumping Unicode stdin: @@ -6992,6 +7058,172 @@ expected-stdout: 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���| 00000128 EFBE EFEF EFBF EFBF - 000A |����.| --- +name: integer-base-one-3Ar +description: + some sample code for hexdumping +stdin: + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\x${i#16#1}" + done + print + } | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z5 hv + dasc= + while read -ar line; do + typeset -i1 line + line[${#line[*]}]=10 + i=0 + while (( i < ${#line[*]} )); do + hv=${line[i++]} + if (( (pos & 15) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc=$dasc. + else + dasc=$dasc${line[i-1]#1#} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + done + if (( (pos & 15) != 1 )); then + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3 |Hello, World!\..| + 00000010 81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC |................| + 00000020 81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E |................| + 00000030 0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E |................| + 00000040 1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E |. !"#$%&'()*+,-.| + 00000050 2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E |/0123456789:;<=>| + 00000060 3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E |?@ABCDEFGHIJKLMN| + 00000070 4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E |OPQRSTUVWXYZ[\]^| + 00000080 5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E |_`abcdefghijklmn| + 00000090 6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E |opqrstuvwxyz{|}~| + 000000A0 7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E |................| + 000000B0 8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E |................| + 000000C0 9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE |................| + 000000D0 AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE |................| + 000000E0 BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE |................| + 000000F0 CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE |................| + 00000100 DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE |................| + 00000110 EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE |................| + 00000120 FF 0A - |..| +--- +name: integer-base-one-3Wr +description: + some sample code for hexdumping Unicode +stdin: + set -U + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\u${i#16#1}" + done + print + print \\xff # invalid utf-8 + print \\xc2 # invalid 2-byte + print \\xef\\xbf\\xc0 # invalid 3-byte + print \\xc0\\x80 # non-minimalistic + print \\xe0\\x80\\x80 # non-minimalistic + print '�￾￿' # end of range + } | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z7 hv + dasc= + while read -ar line; do + typeset -i1 line + line[${#line[*]}]=10 + i=0 + while (( i < ${#line[*]} )); do + hv=${line[i++]} + if (( (hv < 32) || \ + ((hv > 126) && (hv < 160)) )); then + dch=. + elif (( (hv & 0xFF80) == 0xEF80 )); then + dch=� + else + dch=${line[i-1]#1#} + fi + if (( (pos & 7) == 7 )); then + dasc=$dasc$dch + dch= + elif (( (pos & 7) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + (( (pos++ & 7) == 3 )) && \ + print -n -- '- ' + dasc=$dasc$dch + done + done + if (( pos & 7 )); then + while (( pos & 7 )); do + print -n ' ' + (( (pos++ & 7) == 3 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 0048 0065 006C 006C - 006F 002C 0020 0057 |Hello, W| + 00000008 006F 0072 006C 0064 - 0021 005C 000A 3053 |orld!\.こ| + 00000010 3093 306B 3061 306F - FF01 000A 0001 0002 |んにちは!...| + 00000018 0003 0004 0005 0006 - 0007 0008 0009 000A |........| + 00000020 000B 000C 000D 000E - 000F 0010 0011 0012 |........| + 00000028 0013 0014 0015 0016 - 0017 0018 0019 001A |........| + 00000030 001B 001C 001D 001E - 001F 0020 0021 0022 |..... !"| + 00000038 0023 0024 0025 0026 - 0027 0028 0029 002A |#$%&'()*| + 00000040 002B 002C 002D 002E - 002F 0030 0031 0032 |+,-./012| + 00000048 0033 0034 0035 0036 - 0037 0038 0039 003A |3456789:| + 00000050 003B 003C 003D 003E - 003F 0040 0041 0042 |;<=>?@AB| + 00000058 0043 0044 0045 0046 - 0047 0048 0049 004A |CDEFGHIJ| + 00000060 004B 004C 004D 004E - 004F 0050 0051 0052 |KLMNOPQR| + 00000068 0053 0054 0055 0056 - 0057 0058 0059 005A |STUVWXYZ| + 00000070 005B 005C 005D 005E - 005F 0060 0061 0062 |[\]^_`ab| + 00000078 0063 0064 0065 0066 - 0067 0068 0069 006A |cdefghij| + 00000080 006B 006C 006D 006E - 006F 0070 0071 0072 |klmnopqr| + 00000088 0073 0074 0075 0076 - 0077 0078 0079 007A |stuvwxyz| + 00000090 007B 007C 007D 007E - 007F 0080 0081 0082 |{|}~....| + 00000098 0083 0084 0085 0086 - 0087 0088 0089 008A |........| + 000000A0 008B 008C 008D 008E - 008F 0090 0091 0092 |........| + 000000A8 0093 0094 0095 0096 - 0097 0098 0099 009A |........| + 000000B0 009B 009C 009D 009E - 009F 00A0 00A1 00A2 |..... ¡¢| + 000000B8 00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA |£¤¥¦§¨©ª| + 000000C0 00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2 |«¬­®¯°±²| + 000000C8 00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA |³´µ¶·¸¹º| + 000000D0 00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2 |»¼½¾¿ÀÁÂ| + 000000D8 00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA |ÃÄÅÆÇÈÉÊ| + 000000E0 00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2 |ËÌÍÎÏÐÑÒ| + 000000E8 00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA |ÓÔÕÖרÙÚ| + 000000F0 00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2 |ÛÜÝÞßàáâ| + 000000F8 00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA |ãäåæçèéê| + 00000100 00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2 |ëìíîïðñò| + 00000108 00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA |óôõö÷øùú| + 00000110 00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A |ûüýþÿ.�.| + 00000118 EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80 |�.���.��| + 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���| + 00000128 EFBE EFEF EFBF EFBF - 000A |����.| +--- name: integer-base-one-4 description: Check if ksh93-style base-one integers work @@ -7013,6 +7245,32 @@ expected-stdout: 5 97 6 97 --- +name: integer-base-one-5A +description: + Check to see that we’re NUL and Unicode safe +stdin: + set +U + print 'a\0b\xfdz' >x + read -a y x + read -a y @@ -222,7 +222,7 @@ function smores { done } -# base64 encoder (not NUL safe) and decoder (NUL safe), RFC compliant +# base64 encoder and decoder, RFC compliant, NUL safe function Lb64decode { [[ -o utf8-mode ]]; typeset u=$? set +U @@ -262,18 +262,20 @@ set -A Lb64encode_code -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ function Lb64encode { [[ -o utf8-mode ]]; typeset u=$? set +U - typeset c s="$*" t - [[ -n $s ]] || { s=$(cat;print x); s=${s%x}; } - typeset -i i=0 n=${#s} j v + typeset c s t + if (( $# )); then + read -raN-1 s <<<"$*" + unset s[${#s[*]}-1] + else + read -raN-1 s + fi + typeset -i i=0 n=${#s[*]} j v while (( i < n )); do - c=${s:(i++):1} - (( v = 1#$c << 16 )) - c=${s:(i++):1} - (( j = ${#c} ? 1#$c : 0 )) + (( v = s[i++] << 16 )) + (( j = i < n ? s[i++] : 0 )) (( v |= j << 8 )) - c=${s:(i++):1} - (( j = ${#c} ? 1#$c : 0 )) + (( j = i < n ? s[i++] : 0 )) (( v |= j )) t=$t${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]} c=${Lb64encode_code[v >> 6 & 63]} @@ -297,12 +299,17 @@ typeset -Z11 -Uui16 Lcdbhash_result function Lcdbhash_add { [[ -o utf8-mode ]]; typeset u=$? set +U - typeset s="$*" - [[ -n $s ]] || { s=$(cat;print x); s=${s%x}; } - typeset -i i=0 n=${#s} + typeset s + if (( $# )); then + read -raN-1 s <<<"$*" + unset s[${#s[*]}-1] + else + read -raN-1 s + fi + typeset -i i=0 n=${#s[*]} while (( i < n )); do - ((# Lcdbhash_result = (Lcdbhash_result * 33) ^ 1#${s:(i++):1} )) + ((# Lcdbhash_result = (Lcdbhash_result * 33) ^ s[i++] )) done (( u )) || set -U @@ -318,12 +325,17 @@ typeset -Z11 -Uui16 Loaathash_result function Loaathash_add { [[ -o utf8-mode ]]; typeset u=$? set +U - typeset s="$*" - [[ -n $s ]] || { s=$(cat;print x); s=${s%x}; } - typeset -i i=0 n=${#s} + typeset s + if (( $# )); then + read -raN-1 s <<<"$*" + unset s[${#s[*]}-1] + else + read -raN-1 s + fi + typeset -i i=0 n=${#s[*]} while (( i < n )); do - ((# Loaathash_result = (Loaathash_result + 1#${s:(i++):1}) * + ((# Loaathash_result = (Loaathash_result + s[i++]) * 1025 )) ((# Loaathash_result ^= Loaathash_result >> 6 )) done diff --git a/edit.c b/edit.c index 4974ea2..7910950 100644 --- a/edit.c +++ b/edit.c @@ -25,7 +25,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.211 2011/04/22 12:16:38 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.212 2011/05/29 02:18:49 tg Exp $"); /* * in later versions we might use libtermcap for this, but since external @@ -68,7 +68,7 @@ static char holdbuf[LINE]; /* place to hold last edit buffer */ static int x_getc(void); static void x_putcf(int); -static bool x_mode(bool); +static void x_mode(bool); static int x_do_comment(char *, int, int *); static void x_print_expansions(int, char *const *, bool); static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***); @@ -3180,44 +3180,26 @@ x_lastcp(void) return (xlp); } -static bool +static void x_mode(bool onoff) { static bool x_cur_mode; - bool prev; if (x_cur_mode == onoff) - return (x_cur_mode); - prev = x_cur_mode; + return; x_cur_mode = onoff; if (onoff) { - struct termios cb; + x_mkraw(tty_fd, NULL, false); - cb = tty_state; - - edchars.erase = cb.c_cc[VERASE]; - edchars.kill = cb.c_cc[VKILL]; - edchars.intr = cb.c_cc[VINTR]; - edchars.quit = cb.c_cc[VQUIT]; - edchars.eof = cb.c_cc[VEOF]; + edchars.erase = tty_state.c_cc[VERASE]; + edchars.kill = tty_state.c_cc[VKILL]; + edchars.intr = tty_state.c_cc[VINTR]; + edchars.quit = tty_state.c_cc[VQUIT]; + edchars.eof = tty_state.c_cc[VEOF]; #ifdef VWERASE - edchars.werase = cb.c_cc[VWERASE]; + edchars.werase = tty_state.c_cc[VWERASE]; #endif - cb.c_iflag &= ~(INLCR | ICRNL); - cb.c_lflag &= ~(ISIG | ICANON | ECHO); -#if defined(VLNEXT) && defined(_POSIX_VDISABLE) - /* OSF/1 processes lnext when ~icanon */ - cb.c_cc[VLNEXT] = _POSIX_VDISABLE; -#endif - /* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */ -#if defined(VDISCARD) && defined(_POSIX_VDISABLE) - cb.c_cc[VDISCARD] = _POSIX_VDISABLE; -#endif - cb.c_cc[VTIME] = 0; - cb.c_cc[VMIN] = 1; - - tcsetattr(tty_fd, TCSADRAIN, &cb); #ifdef _POSIX_VDISABLE /* Convert unset values to internal 'unset' value */ @@ -3249,8 +3231,6 @@ x_mode(bool onoff) bind_if_not_bound(0, edchars.quit, XFUNC_noop); } else tcsetattr(tty_fd, TCSADRAIN, &tty_state); - - return (prev); } #if !MKSH_S_NOVI @@ -5338,3 +5318,34 @@ vi_macro_reset(void) } } #endif /* !MKSH_S_NOVI */ + +void +x_mkraw(int fd, struct termios *ocb, bool forread) +{ + struct termios cb; + + if (ocb) + tcgetattr(fd, ocb); + else + ocb = &tty_state; + + cb = *ocb; + if (forread) { + cb.c_lflag &= ~(ICANON) | ECHO; + } else { + cb.c_iflag &= ~(INLCR | ICRNL); + cb.c_lflag &= ~(ISIG | ICANON | ECHO); + } +#if defined(VLNEXT) && defined(_POSIX_VDISABLE) + /* OSF/1 processes lnext when ~icanon */ + cb.c_cc[VLNEXT] = _POSIX_VDISABLE; +#endif + /* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */ +#if defined(VDISCARD) && defined(_POSIX_VDISABLE) + cb.c_cc[VDISCARD] = _POSIX_VDISABLE; +#endif + cb.c_cc[VTIME] = 0; + cb.c_cc[VMIN] = 1; + + tcsetattr(fd, TCSADRAIN, &cb); +} diff --git a/exec.c b/exec.c index 2ae6165..146c8e3 100644 --- a/exec.c +++ b/exec.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.91 2011/05/07 00:51:11 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.92 2011/05/29 02:18:51 tg Exp $"); #ifndef MKSH_DEFAULT_EXECSHELL #define MKSH_DEFAULT_EXECSHELL "/bin/sh" @@ -405,15 +405,30 @@ execute(struct op * volatile t, break; case TCASE: + i = 0; ccp = evalstr(t->str, DOTILDE); - for (t = t->left; t != NULL && t->type == TPAT; t = t->right) - for (ap = (const char **)t->vars; *ap; ap++) - if ((s = evalstr(*ap, DOTILDE|DOPAT)) && - gmatchx(ccp, s, false)) - goto Found; - break; - Found: - rv = execute(t->left, flags & XERROK, xerrok); + for (t = t->left; t != NULL && t->type == TPAT; t = t->right) { + for (ap = (const char **)t->vars; *ap; ap++) { + if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) && + gmatchx(ccp, s, false))) { + rv = execute(t->left, flags & XERROK, + xerrok); + i = 0; + switch (t->u.charflag) { + case '&': + i = 1; + /* FALLTHROUGH */ + case '|': + goto TCASE_next; + } + goto TCASE_out; + } + } + i = 0; + TCASE_next: + /* empty */; + } + TCASE_out: break; case TBRACE: diff --git a/funcs.c b/funcs.c index c09f573..3ba148b 100644 --- a/funcs.c +++ b/funcs.c @@ -38,7 +38,7 @@ #endif #endif -__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.186 2011/05/06 15:41:23 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.187 2011/05/29 02:18:51 tg Exp $"); #if HAVE_KILLPG /* @@ -1759,58 +1759,96 @@ c_wait(const char **wp) int c_read(const char **wp) { - int c, ecode = 0, fd = 0, optc; - bool expande = true, historyr = false, expanding; - const char *cp, *emsg; - struct shf *shf; - XString cs, xs = { NULL, NULL, 0, NULL}; - struct tbl *vp; - char *ccp, *xp = NULL, *wpalloc = NULL, delim = '\n'; +#define is_ifsws(c) (ctype((c), C_IFS) && ctype((c), C_IFSWS)) static char REPLY[] = "REPLY"; + int c, fd = 0, rv = 0; + bool savehist = false, intoarray = false, aschars = false; + bool rawmode = false, expanding = false, lastparm = false; + enum { LINES, BYTES, UPTO, READALL } readmode = LINES; + char delim = '\n'; + size_t bytesleft = 128, bytesread; + struct tbl *vp /* FU gcc */ = NULL, *vq; + char *cp, *allocd = NULL, *xp; + const char *ccp; + XString xs; + struct termios tios; + bool restore_tios = false; +#if HAVE_SELECT + bool hastimeout = false; + struct timeval tv, tvlim; +#define c_read_opts "Aad:N:n:prst:u," +#else +#define c_read_opts "Aad:N:n:prsu," +#endif - while ((optc = ksh_getopt(wp, &builtin_opt, "d:prsu,")) != -1) - switch (optc) { - case 'd': - delim = builtin_opt.optarg[0]; - break; - case 'p': - if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { - bi_errorf("%s: %s", "-p", emsg); - return (1); - } - break; - case 'r': - expande = false; - break; - case 's': - historyr = true; - break; - case 'u': - if (!*(cp = builtin_opt.optarg)) - fd = 0; - else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) { - bi_errorf("%s: %s: %s", "-u", cp, emsg); - return (1); - } - break; - case '?': - return (1); + while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1) + switch (c) { + case 'a': + aschars = true; + /* FALLTHROUGH */ + case 'A': + intoarray = true; + break; + case 'd': + delim = builtin_opt.optarg[0]; + break; + case 'N': + case 'n': + readmode = c == 'N' ? BYTES : UPTO; + if (!bi_getn(builtin_opt.optarg, &c)) + return (2); + if (c == -1) { + readmode = READALL; + bytesleft = 1024; + } else + bytesleft = (unsigned int)c; + break; + case 'p': + if ((fd = coproc_getfd(R_OK, &ccp)) < 0) { + bi_errorf("%s: %s", "-p", ccp); + return (2); } + break; + case 'r': + rawmode = true; + break; + case 's': + savehist = true; + break; +#if HAVE_SELECT + case 't': + if (parse_usec(builtin_opt.optarg, &tv)) { + bi_errorf("%s: %s '%s'", T_synerr, strerror(errno), + builtin_opt.optarg); + return (2); + } + hastimeout = true; + break; +#endif + case 'u': + if (!builtin_opt.optarg[0]) + fd = 0; + else if ((fd = check_fd(builtin_opt.optarg, R_OK, &ccp)) < 0) { + bi_errorf("%s: %s: %s", "-u", builtin_opt.optarg, ccp); + return (2); + } + break; + case '?': + return (2); + } wp += builtin_opt.optind; - if (*wp == NULL) *--wp = REPLY; - /* - * Since we can't necessarily seek backwards on non-regular files, - * don't buffer them so we can't read too much. - */ - shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare); + if (intoarray && wp[1] != NULL) { + bi_errorf("too many arguments"); + return (2); + } - if ((cp = cstrchr(*wp, '?')) != NULL) { - strdupx(wpalloc, *wp, ATEMP); - wpalloc[cp - *wp] = '\0'; - *wp = wpalloc; + if ((ccp = cstrchr(*wp, '?')) != NULL) { + strdupx(allocd, *wp, ATEMP); + allocd[ccp - *wp] = '\0'; + *wp = allocd; if (isatty(fd)) { /* * AT&T ksh says it prints prompt on fd if it's open @@ -1818,133 +1856,268 @@ c_read(const char **wp) * (it also doesn't check the interactive flag, * as is indicated in the Korn Shell book). */ - shellf("%s", cp + 1); + shf_puts(ccp + 1, shl_out); } } - /* - * If we are reading from the co-process for the first time, - * make sure the other side of the pipe is closed first. This allows - * the detection of eof. - * - * This is not compatible with AT&T ksh... the fd is kept so another - * coproc can be started with same output, however, this means eof - * can't be detected... This is why it is closed here. - * If this call is removed, remove the eof check below, too. - * coproc_readw_close(fd); + Xinit(xs, xp, bytesleft, ATEMP); + + if (readmode == LINES) + bytesleft = 1; + else if (isatty(fd)) { + x_mkraw(fd, &tios, true); + restore_tios = true; + } + +#if HAVE_SELECT + if (hastimeout) { + gettimeofday(&tvlim, NULL); + timeradd(&tvlim, &tv, &tvlim); + } +#endif + + c_read_readloop: +#if HAVE_SELECT + if (hastimeout) { + fd_set fdset; + + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + gettimeofday(&tv, NULL); + timersub(&tvlim, &tv, &tv); + if (tv.tv_sec < 0) { + /* timeout expired globally */ + rv = 1; + goto c_read_out; + } + + switch (select(fd + 1, &fdset, NULL, NULL, &tv)) { + case 1: + break; + case 0: + /* timeout expired for this call */ + rv = 1; + goto c_read_out; + default: + bi_errorf("%s: %s", T_select, strerror(errno)); + rv = 2; + goto c_read_out; + } + } +#endif + + bytesread = blocking_read(fd, xp, bytesleft); + if (bytesread == (size_t)-1) { + /* interrupted */ + if (errno == EINTR && fatal_trap_check()) { + /* + * Was the offending signal one that would + * normally kill a process? If so, pretend + * the read was killed. + */ + rv = 2; + goto c_read_out; + } + /* just ignore the signal */ + goto c_read_readloop; + } + + switch (readmode) { + case READALL: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + goto c_read_readdone; + } + xp += bytesread; + XcheckN(xs, xp, bytesleft); + break; + + case UPTO: + if (bytesread == 0) + /* end of file reached */ + rv = 1; + xp += bytesread; + goto c_read_readdone; + + case BYTES: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + xp = Xstring(xs, xp); + goto c_read_readdone; + } + xp += bytesread; + if ((bytesleft -= bytesread) == 0) + goto c_read_readdone; + break; + case LINES: + if (bytesread == 0) { + /* end of file reached */ + rv = 1; + goto c_read_readdone; + } + if ((c = *xp) == '\0' && !aschars && delim != '\0') { + /* skip any read NULs unless delimiter */ + break; + } + if (expanding) { + expanding = false; + if (c == delim) { + if (Flag(FTALKING_I) && isatty(fd)) { + /* + * set prompt in case this is + * called from .profile or $ENV + */ + set_prompt(PS2, NULL); + pprompt(prompt, 0); + } + /* drop the backslash */ + --xp; + /* and the delimiter */ + break; + } + } else if (c == delim) { + goto c_read_readdone; + } else if (!rawmode && c == '\\') { + expanding = true; + } + Xcheck(xs, xp); + ++xp; + break; + } + goto c_read_readloop; + + c_read_readdone: + bytesread = Xlength(xs, xp); + Xput(xs, xp, '\0'); + + /*- + * state: we finished reading the input and NUL terminated it + * Xstring(xs, xp) -> xp-1 = input string without trailing delim + * rv = 1 if EOF, 0 otherwise (errors handled already) */ - if (historyr) - Xinit(xs, xp, 128, ATEMP); - expanding = false; - Xinit(cs, ccp, 128, ATEMP); - /* initialise to something not EOF or delim or any character */ - c = 0x100; - for (; *wp != NULL; wp++) { - for (ccp = Xstring(cs, ccp); ; ) { - if (c == delim || c == EOF) - break; - /* loop to read one character */ - while (/* CONSTCOND */ 1) { - c = shf_getc(shf); - /* we break unless NUL or EOF, so... */ - if (c == delim) - /* in case delim == NUL */ - break; - if (c == '\0') - /* skip any read NUL byte */ - continue; - if (c == EOF && shf_error(shf) && - shf_errno(shf) == EINTR) { - /* - * Was the offending signal one that - * would normally kill a process? - * If so, pretend the read was killed. - */ - ecode = fatal_trap_check(); + if (rv == 1) { + /* clean up coprocess if needed, on EOF */ + coproc_read_close(fd); + if (readmode == READALL) + /* EOF is no error here */ + rv = 0; + } - /* non fatal (eg, CHLD), carry on */ - if (!ecode) { - shf_clearerr(shf); - continue; - } - } - break; - } - if (historyr) { - Xcheck(xs, xp); - Xput(xs, xp, c); - } - Xcheck(cs, ccp); - if (expanding) { - expanding = false; - if (c == delim) { - c = 0; - if (Flag(FTALKING_I) && isatty(fd)) { - /* - * set prompt in case this is - * called from .profile or $ENV - */ - set_prompt(PS2, NULL); - pprompt(prompt, 0); - } - } else if (c != EOF) - Xput(cs, ccp, c); - continue; - } - if (expande && c == '\\') { - expanding = true; - continue; - } - if (c == delim || c == EOF) - break; - if (ctype(c, C_IFS)) { - if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS)) - continue; - if (wp[1]) - break; - } - Xput(cs, ccp, c); - } - /* strip trailing IFS white space from last variable */ - if (!wp[1]) - while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) && - ctype(ccp[-1], C_IFSWS)) - ccp--; - Xput(cs, ccp, '\0'); + if (savehist) + histsave(&source->line, Xstring(xs, xp), true, false); + + ccp = cp = Xclose(xs, xp); + expanding = false; + XinitN(xs, 128, ATEMP); + if (intoarray) { vp = global(*wp); - /* Must be done before setting export. */ if (vp->flag & RDONLY) { - shf_flush(shf); + c_read_splitro: bi_errorf("%s: %s", *wp, "is read only"); - afree(wpalloc, ATEMP); - return (2); + c_read_spliterr: + rv = 2; + afree(cp, ATEMP); + goto c_read_out; } + /* exporting an array is currently pointless */ + unset(vp, 1); + /* counter for array index */ + c = 0; + } + c_read_splitloop: + xp = Xstring(xs, xp); + /* generate next word */ + if (!bytesread) { + /* no more input */ + if (intoarray) + goto c_read_splitdone; + /* zero out next parameters */ + goto c_read_gotword; + } + if (aschars) { + Xput(xs, xp, '1'); + Xput(xs, xp, '#'); + bytesleft = utf_ptradj(ccp); + while (bytesleft && bytesread) { + *xp++ = *ccp++; + --bytesleft; + --bytesread; + } + if (xp[-1] == '\0') { + xp[-1] = '0'; + xp[-3] = '2'; + } + goto c_read_gotword; + } + + if (!intoarray && wp[1] == NULL) + lastparm = true; + + /* skip initial IFS whitespace */ + while (is_ifsws(*ccp)) { + ++ccp; + --bytesread; + } + /* copy until IFS character */ + while (bytesread) { + char ch; + + ch = *ccp++; + --bytesread; + if (expanding) { + expanding = false; + } else if (ctype(ch, C_IFS)) { + if (!lastparm) + break; + /* last parameter, copy all */ + } else if (!rawmode && ch == '\\') { + expanding = true; + continue; + } + Xcheck(xs, xp); + Xput(xs, xp, ch); + } + if (lastparm) { + /* remove trailing IFS whitespace */ + while (Xlength(xs, xp) && is_ifsws(xp[-1])) + --xp; + } + c_read_gotword: + Xput(xs, xp, '\0'); + if (intoarray) { + vq = arraysearch(vp, c++); + } else { + vq = global(*wp); + /* must be checked before exporting */ + if (vq->flag & RDONLY) + goto c_read_splitro; if (Flag(FEXPORT)) typeset(*wp, EXPORT, 0, 0, 0); - if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) { - shf_flush(shf); - afree(wpalloc, ATEMP); - return (1); - } } - - shf_flush(shf); - if (historyr) { - Xput(xs, xp, '\0'); - histsave(&source->line, Xstring(xs, xp), true, false); - Xfree(xs, xp); + if (!setstr(vq, Xstring(xs, xp), KSH_RETURN_ERROR)) + goto c_read_spliterr; + if (aschars) { + setint_v(vq, vq, false); + /* protect from UTFMODE changes */ + vq->type = 0; } - /* - * if this is the co-process fd, close the file descriptor - * (can get eof if and only if all processes are have died, - * i.e. coproc.njobs is 0 and the pipe is closed). - */ - if (c == EOF && !ecode) - coproc_read_close(fd); + if (intoarray || *++wp != NULL) + goto c_read_splitloop; - afree(wpalloc, ATEMP); - return (ecode ? ecode : c == EOF); + c_read_splitdone: + /* free up */ + afree(cp, ATEMP); + + c_read_out: + afree(allocd, ATEMP); + Xfree(xs, xp); + if (restore_tios) + tcsetattr(fd, TCSADRAIN, &tios); + return (rv); +#undef is_ifsws } int diff --git a/lex.c b/lex.c index 3b56fb3..158a245 100644 --- a/lex.c +++ b/lex.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.150 2011/05/07 00:51:12 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.151 2011/05/29 02:18:52 tg Exp $"); /* * states while lexing word @@ -1018,6 +1018,10 @@ yylex(int cf) /* c == '(' ) */ MDPAREN; else if (c == '|' && c2 == '&') c = COPROC; + else if (c == ';' && c2 == '|') + c = BRKEV; + else if (c == ';' && c2 == '&') + c = BRKFT; else ungetsc(c2); } else if (c == '\n') { diff --git a/misc.c b/misc.c index 2529066..34ab09a 100644 --- a/misc.c +++ b/misc.c @@ -29,7 +29,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.165 2011/05/04 23:16:02 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.166 2011/05/29 02:18:53 tg Exp $"); /* type bits for unsigned char */ unsigned char chtypes[UCHAR_MAX + 1]; @@ -1641,14 +1641,17 @@ c_cd(const char **wp) bool physical = tobool(Flag(FPHYSICAL)); /* was a node from cdpath added in? */ int cdnode; - /* print where we cd'd? */ - bool printpath = false; + /* show where we went?, error for $PWD */ + bool printpath = false, eflag = false; struct tbl *pwd_s, *oldpwd_s; XString xs; char *dir, *allocd = NULL, *tryp, *pwd, *cdpath; - while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) + while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1) switch (optc) { + case 'e': + eflag = true; + break; case 'L': physical = false; break; @@ -1656,13 +1659,13 @@ c_cd(const char **wp) physical = true; break; case '?': - return (1); + return (2); } wp += builtin_opt.optind; if (Flag(FRESTRICTED)) { bi_errorf("restricted shell - can't cd"); - return (1); + return (2); } pwd_s = global("PWD"); @@ -1672,7 +1675,7 @@ c_cd(const char **wp) /* No arguments - go home */ if ((dir = str_val(global("HOME"))) == null) { bi_errorf("no home directory (HOME not set)"); - return (1); + return (2); } } else if (!wp[1]) { /* One argument: - or dir */ @@ -1683,7 +1686,7 @@ c_cd(const char **wp) dir = str_val(oldpwd_s); if (dir == null) { bi_errorf("no OLDPWD"); - return (1); + return (2); } printpath = true; } @@ -1694,7 +1697,7 @@ c_cd(const char **wp) if (!current_wd[0]) { bi_errorf("can't determine current directory"); - return (1); + return (2); } /* * substitute arg1 for arg2 in current path. @@ -1704,7 +1707,7 @@ c_cd(const char **wp) */ if ((cp = strstr(current_wd, wp[0])) == NULL) { bi_errorf("bad substitution"); - return (1); + return (2); } /*- * ilen = part of current_wd before wp[0] @@ -1723,7 +1726,7 @@ c_cd(const char **wp) printpath = true; } else { bi_errorf("too many arguments"); - return (1); + return (2); } #ifdef NO_PATH_MAX @@ -1750,9 +1753,12 @@ c_cd(const char **wp) else bi_errorf("%s: %s", tryp, strerror(errno)); afree(allocd, ATEMP); - return (1); + Xfree(xs, xp); + return (2); } + rv = 0; + /* allocd (above) => dir, which is no longer used */ afree(allocd, ATEMP); allocd = NULL; @@ -1770,8 +1776,14 @@ c_cd(const char **wp) if (Xstring(xs, xp)[0] != '/') { pwd = NULL; - } else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp)))) + } else if (!physical) { + goto norealpath_PWD; + } else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) { + if (eflag) + rv = 1; + norealpath_PWD: pwd = Xstring(xs, xp); + } /* Set PWD */ if (pwd) { @@ -1784,12 +1796,15 @@ c_cd(const char **wp) set_current_wd(null); pwd = Xstring(xs, xp); /* XXX unset $PWD? */ + if (eflag) + rv = 1; } if (printpath || cdnode) shprintf("%s\n", pwd); afree(allocd, ATEMP); - return (0); + Xfree(xs, xp); + return (rv); } diff --git a/mksh.1 b/mksh.1 index b517783..4611ff2 100644 --- a/mksh.1 +++ b/mksh.1 @@ -1,4 +1,4 @@ -.\" $MirOS: src/bin/mksh/mksh.1,v 1.260 2011/05/06 15:41:24 tg Exp $ +.\" $MirOS: src/bin/mksh/mksh.1,v 1.261 2011/05/29 02:18:54 tg Exp $ .\" $OpenBSD: ksh.1,v 1.140 2011/04/23 10:14:59 sobrado Exp $ .\"- .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, @@ -72,7 +72,7 @@ .\" with -mandoc, it might implement .Mx itself, but we want to .\" use our own definition. And .Dd must come *first*, always. .\" -.Dd $Mdocdate: May 6 2011 $ +.Dd $Mdocdate: May 29 2011 $ .\" .\" Check which macro package we use .\" @@ -391,8 +391,11 @@ is used to create asynchronous pipelines; and .Ql \*(Ba\*(Ba are used to specify conditional execution; -.Ql ;; -is used in +.Ql ;; , +.Ql ;&\& +and +.Ql ;\*(Ba\& +are used in .Ic case statements; .Ql \&(( .. )) @@ -642,10 +645,12 @@ and are reserved words, not meta-characters. .It Xo case Ar word No in .Oo Op \&( -.Ar \ pattern -.Op \*(Ba Ar pattern -.No ... No ) -.Ar list No ;;\ \& Oc ... esac +.Ar pattern +.Op \*(Ba Ar pat +.No ... Ns ) +.Ar list +.Op ;; \*(Ba ;&\& \*(Ba ;\*(Ba\ \& +.Oc ... esac .Xc The .Ic case @@ -669,12 +674,22 @@ stripped; any space within a pattern must be quoted. Both the word and the patterns are subject to parameter, command, and arithmetic substitution, as well as tilde substitution. +.Pp For historical reasons, open and close braces may be used instead of .Ic in and .Ic esac e.g.\& -.Ic case $foo { *) echo bar; } . +.Ic case $foo { *) echo bar;; } . +.Pp +The list terminators are +.Ql ;; +(terminate after the list), +.Ql ;&\& +(fall through into the next list) and +.Ql ;\*(Ba\& +(evaluate the remaining pattern-list tuples). +.Pp The exit status of a .Ic case statement is that of the executed @@ -3059,12 +3074,17 @@ option is supported as a no-op. .Pp .It Xo .Ic cd -.Op Fl LP +.Op Fl L +.Op Ar dir +.Xc +.It Xo +.Ic cd +.Fl P Op Fl e .Op Ar dir .Xc .It Xo .Ic chdir -.Op Fl LP +.Op Fl eLP .Op Ar dir .Xc Set the working directory to @@ -3120,15 +3140,21 @@ and .Ev OLDPWD parameters are updated to reflect the current and old working directory, respectively. +If the +.Fl e +option is set for physical filesystem traversal, and +.Ev PWD +could not be set, the exit code is 1; greater than 1 if an +error occurred, 0 otherwise. .Pp .It Xo .Ic cd -.Op Fl LP +.Op Fl eLP .Ar old new .Xc .It Xo .Ic chdir -.Op Fl LP +.Op Fl eLP .Ar old new .Xc The string @@ -3627,36 +3653,96 @@ directories to the root directory) is printed. .Pp .It Xo .Ic read -.Op Fl d Ar delimiter -.Op Fl prsu Ns Op Ar n -.Op Ar parameter ... +.Op Fl A | Fl a +.Op Fl d Ar x +.Oo Fl N Ar z \*(Ba +.Fl n Ar z Oc +.Oo Fl p \*(Ba +.Fl u Ns Op Ar n +.Oc Op Fl t Ar n +.Op Fl rs +.Op Ar p ... .Xc -Reads a line of input from the standard input, separates the line into fields -using the +Reads a line of input, separates the input into fields using the .Ev IFS parameter (see .Sx Substitution -above), and assigns each field to the specified parameters. -Lines are delimited by the first character of -.Ar delimiter , -.Dv NUL -if empty, if -.Fl d -was used, a newline otherwise. -If there are more parameters than fields, the extra parameters are set to -.Dv NULL , -or alternatively, if there are more fields than parameters, the last parameter -is assigned the remaining fields (inclusive of any separating spaces). +above), and assigns each field to the specified parameters +.Ar p . If no parameters are specified, the .Ev REPLY -parameter is used. -If the input line ends in a backslash and the -.Fl r -option was not used, the backslash and the newline are stripped and more input -is read. -If no input is read, +parameter is used to store the result. +With the +.Fl A +and +.Fl a +options, only no or one parameter is accepted. +If there are more parameters than fields, the extra parameters are set to +the empty string or 0; if there are more fields than parameters, the last +parameter is assigned the remaining fields (including the word separators). +.Pp +The options are as follows: +.Bl -tag -width XuXnX +.It Fl A +Store the result into the parameter +.Ar p +(or +.Ev REPLY ) +as array of words. +.It Fl a +Store the result without word splitting into the parameter +.Ar p +(or +.Ev REPLY ) +as array of characters (wide characters if the +.Ic utf8\-mode +option is enacted, octets otherwise). +.It Fl d Ar x +Use the first byte of +.Ar x , +.Dv NUL +if empty, instead of the ASCII newline character as input line delimiter. +.It Fl N Ar z +Instead of reading till end-of-line, read exactly +.Ar z +bytes; less if EOF or a timeout occurs. +.It Fl n Ar z +Instead of reading till end-of-line, read up to +.Ar z +bytes but return as soon as any bytes are read, e.g.\& from a +slow terminal device, or if EOF or a timeout occurs. +.It Fl p +Read from the currently active co-process, see +.Sx Co-processes +above for details on this. +.It Fl u Ns Op Ar n +Read from the file descriptor +.Ar n +(defaults to 0, i.e.\& standard input). +The argument must immediately follow the option character. +.It Fl t Ar n +Interrupt reading after +.Ar n +seconds (specified as positive decimal value with an optional fractional part). +.It Fl r +Normally, the ASCII backslash character escapes the special +meaning of the following character and is stripped from the input; .Ic read -exits with a non-zero status. +does not stop when encountering a backslash-newline sequence and +does not store that newline in the result. +This option enables raw mode, in which backslashes are not processed. +.It Fl s +The input line is saved to the history. +.El +.Pp +If the input is a terminal, both the +.Fl N +and +.Fl n +options set it into raw mode; +they read an entire file if \-1 is passed as +.Ar z +argument. .Pp The first parameter may have a question mark and a string appended to it, in which case the string is used as a prompt (printed to standard error before @@ -3665,20 +3751,9 @@ any input is read) if the input is a (e.g.\& .Ic read nfoo?\*(aqnumber of foos: \*(aq ) . .Pp -The -.Fl u Ns Ar n -and -.Fl p -options cause input to be read from file descriptor -.Ar n -.Pf ( Ar n -defaults to 0 if omitted) -or the current co-process (see -.Sx Co-processes -above for comments on this), respectively. -If the -.Fl s -option is used, input is saved to the history file. +If no input is read or a timeout occurred, +.Ic read +exits with a non-zero status. .Pp Another handy set of tricks: If @@ -3689,6 +3764,17 @@ then leading whitespace will be removed (IFS) and backslashes processed. You might want to use .Ic while IFS= read \-r foo; do ...; done for pristine I/O. +Similarily, when using the +.Fl a +option, use of the +.Fl r +option might be prudent; the same applies for: +.Bd -literal -offset indent +find . -type f -print0 \*(Ba \e + while IFS= read \-d \*(aq\*(aq \-r filename; do + print \-r \-\- "found <${filename#./}>" +done +.Ed .Pp The inner loop will be executed in a subshell and variable changes cannot be propagated if executed in a pipeline: @@ -6153,7 +6239,7 @@ $ /bin/sleep 666 && echo fubar .Ed .Pp This document attempts to describe -.Nm mksh\ R39c+CVS +.Nm mksh\ R40~rc and up, compiled without any options impacting functionality, such as .Dv MKSH_SMALL , diff --git a/sh.h b/sh.h index 0f41078..b9b0e00 100644 --- a/sh.h +++ b/sh.h @@ -151,9 +151,9 @@ #endif #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.469 2011/05/06 15:41:25 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.470 2011/05/29 02:18:55 tg Exp $"); #endif -#define MKSH_VERSION "R39 2011/05/06" +#define MKSH_VERSION "R39 2011/05/28" #ifndef MKSH_INCLUDES_ONLY @@ -1111,10 +1111,14 @@ struct op { */ int lineno; /* TCOM/TFUNC: LINENO for this */ short type; /* operation type, see below */ + /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ union { - /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ - short evalflags; /* TCOM: arg expansion eval() flags */ - short ksh_func; /* TFUNC: function x (vs x()) */ + /* TCOM: arg expansion eval() flags */ + short evalflags; + /* TFUNC: function x (vs x()) */ + short ksh_func; + /* TPAT: termination character */ + char charflag; } u; }; @@ -1392,6 +1396,8 @@ typedef union { #define BANG 278 /* ! */ #define DBRACKET 279 /* [[ .. ]] */ #define COPROC 280 /* |& */ +#define BRKEV 281 /* ;| */ +#define BRKFT 282 /* ;& */ #define YYERRCODE 300 /* flags to yylex */ @@ -1484,6 +1490,7 @@ int x_bind(const char *, const char *, bool, bool); int x_bind(const char *, const char *, bool); #endif void x_init(void); +void x_mkraw(int, struct termios *, bool); int x_read(char *, size_t); /* eval.c */ char *substitute(const char *, int); @@ -1779,6 +1786,7 @@ const char *skip_varname(const char *, int); const char *skip_wdvarname(const char *, int); int is_wdvarname(const char *, int); int is_wdvarassign(const char *); +struct tbl *arraysearch(struct tbl *, uint32_t); char **makenv(void); void change_winsz(void); int array_ref_len(const char *); diff --git a/syn.c b/syn.c index c4e9a04..16aa5b2 100644 --- a/syn.c +++ b/syn.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.64 2011/05/07 00:51:12 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.65 2011/05/29 02:18:57 tg Exp $"); extern short subshell_nesting_level; @@ -619,7 +619,17 @@ casepart(int endtok) t->left = c_list(true); /* Note: POSIX requires the ;; */ if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) - musthave(BREAK, CONTIN|KEYWORD|ALIAS); + switch (symbol) { + default: + syntaxerr(NULL); + case BREAK: + case BRKEV: + case BRKFT: + t->u.charflag = + (symbol == BRKEV) ? '|' : + (symbol == BRKFT) ? '&' : ';'; + ACCEPT; + } return (t); } @@ -768,6 +778,8 @@ const struct tokeninfo { { "&&", LOGAND, false }, { "||", LOGOR, false }, { ";;", BREAK, false }, + { ";|", BRKEV, false }, + { ";&", BRKFT, false }, { "((", MDPAREN, false }, { "|&", COPROC, false }, /* and some special cases... */ @@ -782,8 +794,8 @@ initkeywords(void) struct tbl *p; ktinit(&keywords, APERM, - /* must be 80% of 2^n (currently 20 keywords) */ - 32); + /* must be 80% of 2^n (currently 28 keywords) */ + 64); for (tt = tokentab; tt->name; tt++) { if (tt->reserved) { p = ktenter(&keywords, tt->name, hash(tt->name)); diff --git a/tree.c b/tree.c index 736e977..c4b5003 100644 --- a/tree.c +++ b/tree.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.48 2011/05/07 00:24:35 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.49 2011/05/29 02:18:57 tg Exp $"); #define INDENT 8 @@ -100,7 +100,7 @@ ptree(struct op *t, int indent, struct shf *shf) case TSELECT: case TFOR: fptreef(shf, indent, "%s %s ", - (t->type == TFOR) ? "for" : "select", t->str); + (t->type == TFOR) ? "for" : T_select, t->str); if (t->vars != NULL) { shf_puts("in ", shf); w = (const char **)t->vars; @@ -121,7 +121,8 @@ ptree(struct op *t, int indent, struct shf *shf) (w[1] != NULL) ? '|' : ')'); ++w; } - fptreef(shf, indent + INDENT, "%N%T%N;;", t1->left); + fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left, + t1->u.charflag); } fptreef(shf, indent, "%Nesac "); break; @@ -949,7 +950,7 @@ dumptree(struct shf *shf, struct op *t) shf_putc(')', shf); shf_putc('\n', shf); dumptree(shf, t1->left); - shf_fprintf(shf, " /%d]", i++); + shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++); } break; OPEN(TWHILE) diff --git a/var.c b/var.c index 3b264f9..8b79c6d 100644 --- a/var.c +++ b/var.c @@ -26,7 +26,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/var.c,v 1.121 2011/05/07 02:02:47 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var.c,v 1.122 2011/05/29 02:18:57 tg Exp $"); /* * Variables @@ -50,7 +50,6 @@ static void setspec(struct tbl *); static void unsetspec(struct tbl *); static int getint(struct tbl *, mksh_ari_t *, bool); static mksh_ari_t intval(struct tbl *); -static struct tbl *arraysearch(struct tbl *, uint32_t); static const char *array_index_calc(const char *, bool *, uint32_t *); uint8_t set_refflag = 0; @@ -348,6 +347,8 @@ str_val(struct tbl *vp) n = (vp->val.i < 0) ? -vp->val.i : vp->val.i; base = (vp->type == 0) ? 10 : vp->type; + if (base == 1 && n == 0) + base = 2; if (base == 1) { size_t sz = 1; @@ -478,8 +479,6 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith) return (vp->type); } s = vp->val.s + vp->type; - if (s == NULL) /* redundant given initial test */ - s = null; base = 10; num = 0; neg = 0; @@ -553,10 +552,12 @@ setint_v(struct tbl *vq, struct tbl *vp, bool arith) return (NULL); if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) { vq->flag &= ~ALLOC; + vq->type = 0; afree(vq->val.s, vq->areap); } vq->val.i = num; - if (vq->type == 0) /* default base */ + if (vq->type == 0) + /* default base */ vq->type = base; vq->flag |= ISSET|INTEGER; if (vq->flag&SPECIAL) @@ -1240,7 +1241,7 @@ unsetspec(struct tbl *vp) * Search for (and possibly create) a table entry starting with * vp, indexed by val. */ -static struct tbl * +struct tbl * arraysearch(struct tbl *vp, uint32_t val) { struct tbl *prev, *curr, *news;