Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: TCP function improvements



Here's an assortment of improvements to the TCP functions which I've
been playing with (as well as using regularly).  There are also one or
two minor fixes I noticed.


New functions tcp_point and tcp_shoot provide one-shot file transfer
without any session.

tcp_spam can take option -e to evaluate a line.

tcp_on_read associative array provides pattern triggers on data read.

%c in TCP prompts provides way of output different text for current or
uncurrent sessions by using the ternary expression enhancement to the
zformat builtin.

SECONDS is used as floating point for greater timing accuracy.  Use
TCP_SECONDS_START to provide workaround if this makes global SECONDS
invisible.

Variables were incorrectly put into the function index.

Sanity check for existing session in tcp_send.

Index: Doc/Zsh/tcpsys.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/tcpsys.yo,v
retrieving revision 1.3
diff -u -r1.3 tcpsys.yo
--- Doc/Zsh/tcpsys.yo	25 Apr 2003 11:19:09 -0000	1.3
+++ Doc/Zsh/tcpsys.yo	4 Jul 2003 16:13:47 -0000
@@ -353,7 +353,7 @@
 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 @@
 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 @@
 )
 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 @@
 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 @@
 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 @@
 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,44 +610,85 @@
 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()
 
Index: Functions/TCP/tcp_expect
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_expect,v
retrieving revision 1.1
diff -u -r1.1 tcp_expect
--- Functions/TCP/tcp_expect	6 Feb 2003 12:21:51 -0000	1.1
+++ Functions/TCP/tcp_expect	4 Jul 2003 16:13:47 -0000
@@ -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
Index: Functions/TCP/tcp_open
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_open,v
retrieving revision 1.2
diff -u -r1.2 tcp_open
--- Functions/TCP/tcp_open	23 Feb 2003 23:24:32 -0000	1.2
+++ Functions/TCP/tcp_open	4 Jul 2003 16:13:47 -0000
@@ -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
Index: Functions/TCP/tcp_output
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_output,v
retrieving revision 1.2
diff -u -r1.2 tcp_output
--- Functions/TCP/tcp_output	23 Feb 2003 23:24:32 -0000	1.2
+++ Functions/TCP/tcp_output	4 Jul 2003 16:13:47 -0000
@@ -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 @@
 # 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
Index: Functions/TCP/tcp_point
===================================================================
RCS file: Functions/TCP/tcp_point
diff -N Functions/TCP/tcp_point
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Functions/TCP/tcp_point	4 Jul 2003 16:13:47 -0000
@@ -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
Index: Functions/TCP/tcp_read
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_read,v
retrieving revision 1.2
diff -u -r1.2 tcp_read
--- Functions/TCP/tcp_read	25 Apr 2003 11:19:10 -0000	1.2
+++ Functions/TCP/tcp_read	4 Jul 2003 16:13:47 -0000
@@ -60,7 +60,7 @@
 
 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 @@
 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 @@
 
     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
Index: Functions/TCP/tcp_send
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_send,v
retrieving revision 1.1
diff -u -r1.1 tcp_send
--- Functions/TCP/tcp_send	6 Feb 2003 12:21:51 -0000	1.1
+++ Functions/TCP/tcp_send	4 Jul 2003 16:13:47 -0000
@@ -3,6 +3,7 @@
 
 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 @@
 
 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 @@
 	tcp_output -P "$TCP_OUTPUT" -S $TCP_SESS -F $fd -q "${(j. .)*}"
     fi
 done
+
+return $mystat
Index: Functions/TCP/tcp_shoot
===================================================================
RCS file: Functions/TCP/tcp_shoot
diff -N Functions/TCP/tcp_shoot
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Functions/TCP/tcp_shoot	4 Jul 2003 16:13:47 -0000
@@ -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
Index: Functions/TCP/tcp_spam
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_spam,v
retrieving revision 1.4
diff -u -r1.4 tcp_spam
--- Functions/TCP/tcp_spam	2 May 2003 10:59:07 -0000	1.4
+++ Functions/TCP/tcp_spam	4 Jul 2003 16:13:47 -0000
@@ -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 @@
 
 if [[ -n $transmit ]]; then
   cmd=tcp_send
-else
+elif [[ -z $eval ]]; then
   cmd=$1
   shift
 fi
@@ -95,7 +100,18 @@
     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
Index: Functions/TCP/tcp_wait
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/TCP/tcp_wait,v
retrieving revision 1.1
diff -u -r1.1 tcp_wait
--- Functions/TCP/tcp_wait	6 Feb 2003 12:21:51 -0000	1.1
+++ Functions/TCP/tcp_wait	4 Jul 2003 16:13:47 -0000
@@ -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

-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR Ltd., Science Park, Milton Road,
Cambridge, CB4 0WH, UK                          Tel: +44 (0)1223 692070


**********************************************************************
The information transmitted is intended only for the person or
entity to which it is addressed and may contain confidential 
and/or privileged material. 
Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by 
persons or entities other than the intended recipient is 
prohibited.  
If you received this in error, please contact the sender and 
delete the material from any computer.
**********************************************************************



Messages sorted by: Reverse Date, Date, Thread, Author