about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--Doc/Zsh/tcpsys.yo126
-rw-r--r--Functions/TCP/tcp_expect8
-rw-r--r--Functions/TCP/tcp_open10
-rw-r--r--Functions/TCP/tcp_output9
-rw-r--r--Functions/TCP/tcp_point29
-rw-r--r--Functions/TCP/tcp_read24
-rw-r--r--Functions/TCP/tcp_send8
-rw-r--r--Functions/TCP/tcp_shoot21
-rw-r--r--Functions/TCP/tcp_spam28
-rw-r--r--Functions/TCP/tcp_wait9
11 files changed, 242 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index 6cd615b47..1c6bf0f40 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2003-07-04  Peter Stephenson  <pws@csr.com>
 
+	* 18810: Doc/Zsh/tcpsys.yo, Functions/TCP/tcp_expect,
+	Functions/TCP/tcp_open, Functions/TCP/tcp_output,
+	Functions/TCP/tcp_point, Functions/TCP/tcp_read,
+	Functions/TCP/tcp_send, Functions/TCP/tcp_shoot,
+	Functions/TCP/tcp_spam, Functions/TCP/tcp_wait: Various
+	enhancements and bug fixes for the TCP function suite.
+
 	* 18571: Doc/Zsh/params.yo: Note easy way of replacing
 	associative array elements using +=.  (Posted ages ago
 	and forgotten about.)
diff --git a/Doc/Zsh/tcpsys.yo b/Doc/Zsh/tcpsys.yo
index 2875b8981..1b52b4f4b 100644
--- a/Doc/Zsh/tcpsys.yo
+++ b/Doc/Zsh/tcpsys.yo
@@ -353,7 +353,7 @@ The command is run in the background, so tt(tcp_proxy) can then accept new
 connections.  It continues to accept new connections until interrupted.
 )
 findex(tcp_spam)
-item(tt(tcp_spam [-rtv] [ -a | -s ) var(sess) tt(| -l) var(sess)tt(,... ]) var(cmd) tt(...))(
+item(tt(tcp_spam [-ertv] [ -a | -s ) var(sess) tt(| -l) var(sess)tt(,... ]) var(cmd) tt(...))(
 Execute `var(cmd) tt(...)' for each session in turn.  Note this executes
 the command and arguments; it does not send the command line as data
 unless the tt(-t) (transmit) option is given.
@@ -374,6 +374,9 @@ The tt(-v) flag specifies that a tt($TCP_PROMPT) will be output before each
 session.  This is output after any modification to TCP_SESS by the
 user-defined tt(tcp_on_spam) function described below.  (Obviously that
 function is able to generate its own output.)
+
+If the option tt(-e) is present, the line given as var(cmd ...) is executed
+using tt(eval), otherwise it is executed without any further processing.
 )
 findex(tcp_talk)
 item(tt(tcp_talk))(
@@ -403,7 +406,33 @@ installed.
 )
 enditem()
 
-sect(TCP User-defined Function)
+subsect(`One-shot' file transfer)
+startitem()
+xitem(tt(tcp_point) var(port))
+item(tt(tcp_shoot) var(host) var(port))(
+This pair of functions provide a simple way to transfer a file between
+two hosts within the shell.  Note, however, that bulk data transfer is
+currently done using tt(cat).  tt(tcp_point) reads any data arriving at
+var(port) and sends it to standard output; tt(tcp_shoot) connects to
+var(port) on var(host) and sends its standard input.  Any unused var(port)
+may be used; the standard mechanism for picking a port is to think of a
+random four-digit number above 1024 until one works.
+
+To transfer a file from host tt(woodcock) to host tt(springes), on
+tt(springes):
+
+example(tcp_point 8091 >output_file)
+
+and on tt(woodcock):
+
+example(tcp_shoot springes 8091 <input_file)
+
+As these two functions do not require tt(tcp_open) to set up a TCP
+connection first, they may need to be autoloaded separately.
+)
+enditem()
+
+sect(TCP User-defined Functions)
 
 Certain functions, if defined by the user, will be called by the function
 system in certain contexts.  This facility depends on the module
@@ -485,9 +514,23 @@ output, from within tt(tcp_read) and (if tt($TCP_OUTPUT) is set)
 tt(tcp_send).
 
 The var(prompt) to use is specified by tt(-P); the default is the empty
-string.  It can contain `tt(%s)' which is replaced by the session name, or
-`tt(%f)' which is replaced by the session's file descriptor; `tt(%%)' is
-replaced by a single `tt(%)'.
+string.  It can contain:
+startitem()
+item(tt(%c))(
+Expands to 1 if the session is the current session, otherwise 0.  Used
+with ternary expresions such as `tt(%LPAR()c.-.PLUS()RPAR())' to
+output `tt(PLUS())' for the current session and `tt(-)' otherwise.
+)
+item(tt(%f))(
+Replaced by the session's file descriptor.
+)
+item(tt(%s))(
+Replaced by the session name.
+)
+item(tt(%%))(
+Replaced by a single `tt(%)'.
+)
+enditem()
 
 The option tt(-q) suppresses output to standard output, but not to any log
 files which are configured.
@@ -514,33 +557,33 @@ setting outside the function.  Likewise, `tt(local TCP_SESS=)var(sess)'
 sets a session for the duration of a function.
 
 startitem()
-findex(tcp_expect_lines)
+vindex(tcp_expect_lines)
 item(tt(tcp_expect_lines))(
 Array.  The set of lines read during the last call to tt(tcp_expect),
 including the last (tt($TCP_LINE)).
 )
-findex(tcp_filter)
+vindex(tcp_filter)
 item(tt(tcp_filter))(
 Array. May be set directly.  A set of extended globbing patterns which,
 if matched in tt(tcp_output), will cause the line not to be printed to
 standard output.  The patterns should be defined as described for the
 arguments to tt(tcp_expect).  Output of line to log files is not affected.
 )
-findex(TCP_LINE)
+vindex(TCP_LINE)
 item(tt(TCP_LINE))(
 The last line read by tt(tcp_read), and hence also tt(tcp_expect).
 )
-findex(TCP_LINE_FD)
+vindex(TCP_LINE_FD)
 item(tt(TCP_LINE_FD))(
 The file descriptor from which tt($TCP_LINE) was read.
 tt(${tcp_by_fd[$TCP_LINE_FD]}) will give the corresponding session name.
 )
-findex(tcp_lines)
+vindex(tcp_lines)
 item(tt(tcp_lines))(
 Array. The set of lines read during the last call to tt(tcp_read),
 including the last (tt($TCP_LINE)).
 )
-findex(TCP_LOG)
+vindex(TCP_LOG)
 item(tt(TCP_LOG))(
 May be set directly, although it is also controlled by tt(tcp_log).
 The name of a file to which output from all sessions will be sent.
@@ -555,11 +598,11 @@ will be sent; the full filename is tt(${TCP_LOG_SESS}.)var(sess).
 Output to each file is raw; no prompt is added.  If it is not an absolute
 path name, it will follow the user's current directory.
 )
-findex(tcp_nospam_list)
+vindex(tcp_nospam_list)
 item(tt(tcp_nospam_list))(
 Array.  May be set directly.  See tt(tcp_spam) for how this is used.
 )
-findex(TCP_OUTPUT)
+vindex(TCP_OUTPUT)
 item(tt(TCP_OUTPUT))(
 May be set directly.  If a non-empty string, any data sent to a session by
 tt(tcp_send) will be logged.  The prompt has the same format as
@@ -567,47 +610,88 @@ tt(TCP_PROMPT) and the same rules for its use apply:  it is used in a file
 specified by tt($TCP_LOG), but not in a file generated from
 tt($TCP_LOG_SESS).
 )
-findex(TCP_PROMPT)
+vindex(TCP_PROMPT)
 item(tt(TCP_PROMPT))(
 May be set directly.  Used as the prefix for data read by tt(tcp_read)
 which is printed to standard output or to the log file given by
 tt($TCP_LOG), if any.  Any `tt(%s)', `tt(%f)' or `tt(%%)' occurring in the
 string will be replaced by the name of the session, the session's
-underlying file descriptor, or a single `tt(%)', respectively.
+underlying file descriptor, or a single `tt(%)', respectively.  The
+expression `tt(%c)' expands to 1 if the session being read is the current
+session, else 0; this is most useful in ternary expressions such as
+`tt(%LPAR()c.-.PLUS()RPAR())' which outputs `tt(PLUS())' if the session is
+the current one, else `tt(-)'.
 )
-findex(TCP_READ_DEBUG)
+vindex(TCP_READ_DEBUG)
 item(tt(TCP_READ_DEBUG))(
 May be set directly.  If this has non-zero length, tt(tcp_read) will give
 some limited diagnostics about data being read.
 )
-findex(TCP_SESS)
+vindex(TCP_SECONDS_START)
+item(tt(TCP_SECONDS_START))(
+This value is created and initialised to zero by tcp_open.
+
+The functions tt(tcp_read) and tt(tcp_expect) use the shell's
+tt(SECONDS) parameter for their own timing purposes.  If that parameter
+is not of floating point type on entry to one of the functions, it will
+create a local parameter tt(SECONDS) which is floating point and set the
+parameter tt(TCP_SECONDS_START) to the previous value of tt($SECONDS).
+If the parameter is already floating point, it is used without a local
+copy being created and tt(TCP_SECONDS_START) is not set.  As the global
+value is zero, the shell elapsed time is guaranteed to be the sum of
+tt($SECONDS) and tt($TCP_SECONDS_START).
+
+This can be avoided by setting tt(SECONDS) globally to a floating point
+value using `tt(typeset -F SECONDS)'; then the TCP functions will never
+make a local copy and never set tt(TCP_SECONDS_START) to a non-zero value.
+)
+vindex(TCP_SESS)
 item(tt(TCP_SESS))(
 May be set directly.  The current session; must refer to one of the
 sessions established by tt(tcp_open).
 )
-findex(TCP_SILENT)
+vindex(TCP_SILENT)
 item(tt(TCP_SILENT))(
 May be set directly, although it is also controlled by tt(tcp_log).
 If of non-zero length, data read by tt(tcp_read) will not be written to
 standard output, though may still be written to a log file.
 )
-findex(tcp_spam_list)
+vindex(tcp_spam_list)
 item(tt(tcp_spam_list))(
 Array.  May be set directly.  See the description of the function
 tt(tcp_spam) for how this is used.
 )
-findex(TCP_TALK_ESCAPE)
+vindex(TCP_TALK_ESCAPE)
 item(tt(TCP_TALK_ESCAPE))(
 May be set directly.  See the description of the function tt(tcp_talk) for
 how this is used.
 )
-findex(TCP_TIMEOUT)
+vindex(TCP_TIMEOUT)
 item(tt(TCP_TIMEOUT))(
 May be set directly.  Currently this is only used by the function
 tt(tcp_command), see above.
 )
 enditem()
 
+sect(TCP User-defined Parameters)
+
+The following parameters are not set by the function system, but have
+a special effect if set by the user.
+
+startitem()
+vindex(tcp_on_read)
+item(tt(tcp_on_read))(
+This should be an associative array; if it is not, the behaviour is
+undefined.  Each key is the name of a shell function or other command,
+and the corresponding value is a shell pattern (using tt(EXTENDED_GLOB)).
+Every line read from a TCP session directly or indirectly using
+tt(tcp_read) (which includes lines read by tt(tcp_expect)) is compared
+against the pattern.  If the line matches, the command given in the key is
+called with two arguments: the name of the session from which the line was
+read, and the line itself.
+)
+enditem()
+
 sect(TCP Utility Parameters)
 
 These parameters are controlled by the function system; they may be read
diff --git a/Functions/TCP/tcp_expect b/Functions/TCP/tcp_expect
index 14963a3e6..e49f93804 100644
--- a/Functions/TCP/tcp_expect
+++ b/Functions/TCP/tcp_expect
@@ -37,8 +37,12 @@
 emulate -L zsh
 setopt extendedglob
 
-# Get extra accuracy by making SECONDS floating point locally
-typeset -F SECONDS
+if [[ ${(t)SECONDS} != float* ]]; then
+    # If called from another function, use that
+    typeset -F TCP_SECONDS_START=$SECONDS
+    # Get extra accuracy by making SECONDS floating point locally
+    typeset -F SECONDS
+fi
 
 # Variables are all named _expect_* to avoid problems with the -p param.
 local _expect_opt _expect_pvar
diff --git a/Functions/TCP/tcp_open b/Functions/TCP/tcp_open
index f762d932a..1f7a65c9a 100644
--- a/Functions/TCP/tcp_open
+++ b/Functions/TCP/tcp_open
@@ -53,11 +53,19 @@
 emulate -L zsh
 setopt extendedglob cbases
 
+# Global set up for TCP function suite.
+
 zmodload -i zsh/net/tcp || return 1
 zmodload -i zsh/zutil
 autoload -U tcp_alias tcp_close tcp_command tcp_expect tcp_fd_handler
 autoload -U tcp_log tcp_output tcp_proxy tcp_read tcp_rename tcp_send
-autoload -U tcp_sess tcp_spam tcp_talk tcp_wait
+autoload -U tcp_sess tcp_spam tcp_talk tcp_wait tcp_point tcp_shoot
+
+# TCP_SECONDS_START is only set if we override TCP_SECONDS locally,
+# so provide a global value for convenience.  Should probably always be 0.
+(( ${+TCP_SECONDS_START} )) || typeset -gF TCP_SECONDS_START
+
+# Processing for new connection.
 
 local opt accept fake nozle sessfile sess quiet
 local -a sessnames sessargs
diff --git a/Functions/TCP/tcp_output b/Functions/TCP/tcp_output
index 69177bae0..781c46c33 100644
--- a/Functions/TCP/tcp_output
+++ b/Functions/TCP/tcp_output
@@ -1,7 +1,7 @@
 emulate -L zsh
 setopt extendedglob
 
-local opt tprompt sess read_fd tpat quiet
+local opt tprompt sess read_fd tpat quiet cursess
 
 while getopts "F:P:qS:" opt; do
   case $opt in
@@ -29,7 +29,12 @@ fi
 # where data is coming from; also, it allows more predictable
 # behaviour in tcp_expect.
 if [[ -n $tprompt ]]; then
-  zformat -f REPLY $tprompt "s:$sess" "f:$read_fd"
+  if [[ $sess = $TCP_SESS ]]; then
+      cursess="c:1"
+  else
+      cursess="c:0"
+  fi
+  zformat -f REPLY $tprompt "s:$sess" "f:$read_fd" $cursess
   # We will pass this back up.
   REPLY="$REPLY$*"
 else
diff --git a/Functions/TCP/tcp_point b/Functions/TCP/tcp_point
new file mode 100644
index 000000000..6a8f75d3a
--- /dev/null
+++ b/Functions/TCP/tcp_point
@@ -0,0 +1,29 @@
+emulate -L zsh
+setopt extendedglob cbases
+
+
+if [[ $# -ne 1 ]]; then
+    print "Usage: $0 port
+Listen on the given port; send anything that arrives to standard output." >&2
+    return 1
+fi
+
+local REPLY lfd afd
+if ! ztcp -l $1; then
+    print "Failed to listen on port $1" >&2
+    return 1
+fi
+
+lfd=$REPLY
+
+if ! ztcp -a $lfd; then
+    print "Failed to accept on fd $lfd" >&2
+    ztcp -c $lfd
+fi
+
+afd=$REPLY
+
+cat <&$afd
+
+ztcp -c $lfd
+ztcp -c $afd
diff --git a/Functions/TCP/tcp_read b/Functions/TCP/tcp_read
index d9aa47e0e..51e5356cc 100644
--- a/Functions/TCP/tcp_read
+++ b/Functions/TCP/tcp_read
@@ -60,7 +60,7 @@ setopt extendedglob cbases
 
 zmodload -i zsh/mathfunc
 
-local opt drain line quiet block read_fd all sess
+local opt drain line quiet block read_fd all sess key val
 local -A read_fds
 read_fds=()
 float timeout timeout_all endtime
@@ -139,8 +139,12 @@ tcp_lines=()
 local helper_stat=2 skip tpat reply REPLY
 float newtimeout
 
-# Get extra accuracy by making SECONDS floating point locally
-typeset -F SECONDS
+if [[ ${(t)SECONDS} != float* ]]; then
+    # If called from another function, don't override
+    typeset -F TCP_SECONDS_START=$SECONDS
+    # Get extra accuracy by making SECONDS floating point locally
+    typeset -F SECONDS
+fi
 
 if (( timeout_all )); then
   (( endtime = SECONDS + timeout_all ))
@@ -194,11 +198,23 @@ while (( ${#read_fds} )); do
 
     helper_stat=0
     sess=${tcp_by_fd[$read_fd]}
-    tcp_output -P "${TCP_PROMPT:=<-[%s] }" -S $sess -F $read_fd \
+    tcp_output -P "${TCP_PROMPT=<-[%s] }" -S $sess -F $read_fd \
       ${TCP_SILENT:+-q} "$line"
     # REPLY is now set to the line with an appropriate prompt.
     tcp_lines+=($REPLY)
     TCP_LINE=$REPLY TCP_LINE_FD=$read_fd
+
+    # Handle user-defined triggers
+    if (( ${+tcp_on_read} )); then
+	# Call the function given in the key for each matching value.
+	# It is this way round because function names must be
+	# unique, while patterns do not need to be.  Furthermore,
+	# this keeps the use of subscripting under control.
+	for key val in ${(kv)tcp_on_read}; do
+	    [[ $line = ${~val} ]] && $key "$sess" "$line"
+	done
+    fi
+
     # Only handle one line from one device at a time unless draining.
     [[ -z $drain ]] && return $stat
   done
diff --git a/Functions/TCP/tcp_send b/Functions/TCP/tcp_send
index e7dfca771..c5f902f71 100644
--- a/Functions/TCP/tcp_send
+++ b/Functions/TCP/tcp_send
@@ -3,6 +3,7 @@ setopt extendedglob cbases
 
 local opt quiet all sess fd nonewline
 local -a sessions write_fds
+integer mystat
 
 while getopts "al:nqs:" opt; do
     case $opt in
@@ -56,6 +57,11 @@ local TCP_SESS
 
 for TCP_SESS in $sessions; do
     fd=${tcp_by_name[$TCP_SESS]}
+    if [[ -z $fd ]]; then
+	print "No such session: $TCP_SESS" >&2
+	mystat=1
+	continue
+    fi
     print $nonewline -r -- $* >&$fd
     if [[ $? -ne 0 || -n $TCP_FD_CLOSED ]]; then
 	print "Session ${TCP_SESS}: fd $fd unusable." >&2
@@ -65,3 +71,5 @@ for TCP_SESS in $sessions; do
 	tcp_output -P "$TCP_OUTPUT" -S $TCP_SESS -F $fd -q "${(j. .)*}"
     fi
 done
+
+return $mystat
diff --git a/Functions/TCP/tcp_shoot b/Functions/TCP/tcp_shoot
new file mode 100644
index 000000000..8ff9866ba
--- /dev/null
+++ b/Functions/TCP/tcp_shoot
@@ -0,0 +1,21 @@
+emulate -L zsh
+setopt extendedglob
+
+local REPLY tfd
+
+if [[ $# -ne 2 ]]; then
+    print "Usage: tcp_dump host port
+Connect to the given host and port; send standard input.">&2
+    return 1
+fi
+
+if ! ztcp $1 $2; then
+    print "Failed to open connection to host $1 port $2" >&2
+    return 1
+fi
+
+tfd=$REPLY
+
+cat >&$tfd
+
+ztcp -c $tfd
diff --git a/Functions/TCP/tcp_spam b/Functions/TCP/tcp_spam
index 8b84cd0d2..345be7f21 100644
--- a/Functions/TCP/tcp_spam
+++ b/Functions/TCP/tcp_spam
@@ -4,6 +4,8 @@
 #    If not given and tcp_spam_list is set to a list of sessions,
 #    only those will be spammed.  If tcp_no_spam_list is set, those
 #    will (also) be excluded from spamming.
+# -e use `eval' to run the command list instead of executing as
+#    a normal command line.
 # -l sess1,sess2    give comma separated list of sessions to spam
 # -r reverse, spam in opposite order (default is alphabetic, -r means
 #    omegapsiic).  Note tcp_spam_list is not sorted (but may be reversed).
@@ -19,14 +21,17 @@
 emulate -L zsh
 setopt extendedglob
 
-local TCP_SESS cmd opt verbose reverse sesslist transmit all
+local cursess=$TCP_SESS sessstr
+local TCP_SESS cmd opt verbose reverse sesslist transmit all eval
 local match mbegin mend REPLY
 local -a sessions
 
-while getopts "al:rtv" opt; do
+while getopts "ael:rtv" opt; do
     case $opt in
 	(a) all=1
 	    ;;
+	(e) eval=1
+	    ;;
 	(l) sessions+=(${(s.,.)OPTARG})
 	    ;;
 	(r) reverse=1
@@ -82,7 +87,7 @@ fi
 
 if [[ -n $transmit ]]; then
   cmd=tcp_send
-else
+elif [[ -z $eval ]]; then
   cmd=$1
   shift
 fi
@@ -95,7 +100,18 @@ for TCP_SESS in $sessions; do
     tcp_on_spam $TCP_SESS $cmd $*
     [[ $REPLY = done ]] && continue
   fi
-  [[ -n $verbose ]] && zformat -f REPLY $TCP_PROMPT "s:$TCP_SESS" \
-    "f:${tcp_by_name[$TCP_SESS]}" && print -r $REPLY
-  eval $cmd '$*'
+  if [[ -n $verbose ]]; then
+      if [[ $TCP_SESS = $cursess ]]; then
+	  sessstr="c:1"
+      else
+	  sessstr="c:0"
+      fi
+      zformat -f REPLY $TCP_PROMPT "s:$TCP_SESS" \
+	  "f:${tcp_by_name[$TCP_SESS]}" $sessstr && print -r $REPLY
+  fi
+  if [[ -n $eval ]]; then
+      eval $*
+  else
+      eval $cmd '$*'
+  fi
 done
diff --git a/Functions/TCP/tcp_wait b/Functions/TCP/tcp_wait
index d18068a66..ee04cf23b 100644
--- a/Functions/TCP/tcp_wait
+++ b/Functions/TCP/tcp_wait
@@ -1,7 +1,14 @@
 # Wait for given number of seconds, reading any data from
 # all TCP connections while doing so.
 
-typeset -F SECONDS to end
+if [[ ${(t)SECONDS} != float* ]]; then
+    # If called from tcp_expect, don't override
+    typeset -F TCP_SECONDS_START=$SECONDS
+    # Get extra accuracy by making SECONDS floating point locally
+    typeset -F SECONDS
+fi
+
+typeset to end
 
 (( to = $1, end = SECONDS + to ))
 while (( SECONDS < end )); do