mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-10-23 04:30:24 +02:00
18202: New TCP function system plus small error message change in ztcp.
This commit is contained in:
parent
809ab19dff
commit
5c1f3b65a6
27 changed files with 2139 additions and 5 deletions
156
Functions/TCP/tcp_alias
Normal file
156
Functions/TCP/tcp_alias
Normal file
|
@ -0,0 +1,156 @@
|
|||
# Create an alias for a TCP session.
|
||||
#
|
||||
# The syntax is similar to the `alias' builtin. Aliases with a trailing
|
||||
# `=' are assigned, while those without are listed.
|
||||
#
|
||||
# The alias can be used to refer to the session, however any output
|
||||
# from the session will be shown using information for the base
|
||||
# session name. Likewise, any other reference to the session's file
|
||||
# descriptor will cause the original session name rather than the alias to
|
||||
# be used.
|
||||
#
|
||||
# It is an error to attempt to create an alias for a non-existent session.
|
||||
# The alias will be removed when the session is closed.
|
||||
#
|
||||
# An alias can be reused without the session having to be closed.
|
||||
# However, a base session name cannot be used as an alias. If this
|
||||
# becomes necessary, the base session should be renamed with tcp_rename
|
||||
# first.
|
||||
#
|
||||
# With no arguments, list aliases.
|
||||
#
|
||||
# With the option -d, delete the alias. No value is allowed in this case.
|
||||
#
|
||||
# With the option -q (quiet), just return status 1 on failure. This
|
||||
# does not apply to bad syntax, which is always reported. Bad syntax
|
||||
# includes deleting aliases when supplying a value.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob cbases
|
||||
|
||||
local opt quiet base value alias delete arg match mbegin mend fd array
|
||||
integer stat index
|
||||
|
||||
while getopts "qd" opt; do
|
||||
case $opt in
|
||||
(q) quiet=1
|
||||
;;
|
||||
(d) delete=1
|
||||
;;
|
||||
(*) return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
if (( ! $# )); then
|
||||
if (( ${#tcp_aliases} )); then
|
||||
for fd value in ${(kv)tcp_aliases}; do
|
||||
for alias in ${=value}; do
|
||||
print -r - \
|
||||
"${alias}: alias for session ${tcp_by_fd[$fd]:-unnamed fd $fd}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
for arg in $*; do
|
||||
if [[ $arg = (#b)([^=]##)=(*) ]]; then
|
||||
if [[ -n $delete ]]; then
|
||||
print "$0: value given with deletion command." >&2
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
alias=$match[1]
|
||||
base=$match[2]
|
||||
if [[ -z $base ]]; then
|
||||
# hmm, this is very nearly a syntax error...
|
||||
[[ -z $quiet ]] && print "$0: empty value for alias $alias" >&2
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
if [[ ${+tcp_by_name} -eq 0 || -z ${tcp_by_name[$base]} ]]; then
|
||||
[[ -z $quiet ]] && print "$0: no base session \`$base' for alias"
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
if [[ -n ${tcp_by_name[$alias]} ]]; then
|
||||
# already exists, OK if this is an alias...
|
||||
fd=${tcp_by_name[$alias]}
|
||||
array=(${=tcp_aliases[$fd]})
|
||||
if [[ -n ${array[(r)$alias]} ]]; then
|
||||
# yes, we're OK; delete the old alias.
|
||||
unset "tcp_by_name[$alias]"
|
||||
index=${array[(i)$alias]}
|
||||
array=(${array[1,index-1]} ${array[index+1,-1]})
|
||||
if [[ -z "$array" ]]; then
|
||||
unset "tcp_aliase[$fd]"
|
||||
else
|
||||
tcp_aliases[$fd]="$array"
|
||||
fi
|
||||
else
|
||||
# oops
|
||||
if [[ -z $quiet ]]; then
|
||||
print "$0: \`$alias' is already a session name." >&2
|
||||
fi
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
(( ! ${+tcp_aliases} )) && typeset -gA tcp_aliases
|
||||
fd=${tcp_by_name[$base]}
|
||||
if [[ -n ${tcp_aliases[$fd]} ]]; then
|
||||
tcp_aliases[$fd]+=" $alias"
|
||||
else
|
||||
tcp_aliases[$fd]=$alias
|
||||
fi
|
||||
tcp_by_name[$alias]=$fd
|
||||
if zmodload -i zsh/parameter; then
|
||||
if (( ${+functions[tcp_on_alias]} )); then
|
||||
tcp_on_alias $alias $fd
|
||||
fi
|
||||
fi
|
||||
else
|
||||
alias=$arg
|
||||
fd=${tcp_by_name[$alias]}
|
||||
if [[ -z $fd ]]; then
|
||||
print "$0: no such alias \`$alias'" >&2
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
# OK if this is an alias...
|
||||
array=(${=tcp_aliases[$fd]})
|
||||
if [[ -n ${array[(r)$alias]} ]]; then
|
||||
# yes, we're OK
|
||||
if [[ -n $delete ]]; then
|
||||
unset "tcp_by_name[$alias]"
|
||||
index=${array[(i)$alias]}
|
||||
array=(${array[1,index-1]} ${array[index+1,-1]})
|
||||
if [[ -z "$array" ]]; then
|
||||
unset "tcp_aliases[$fd]"
|
||||
else
|
||||
tcp_aliases[$fd]="$array"
|
||||
fi
|
||||
|
||||
if zmodload -i zsh/parameter; then
|
||||
if (( ${+functions[tcp_on_unalias]} )); then
|
||||
tcp_on_unalias $alias $fd
|
||||
fi
|
||||
fi
|
||||
else
|
||||
print -r - \
|
||||
"${alias}: alias for session ${tcp_by_fd[$fd]:-unnamed fd $fd}"
|
||||
fi
|
||||
else
|
||||
# oops
|
||||
if [[ -z $quiet ]]; then
|
||||
print "$0: \`$alias' is a session name." >&2
|
||||
fi
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
return $stat
|
134
Functions/TCP/tcp_close
Normal file
134
Functions/TCP/tcp_close
Normal file
|
@ -0,0 +1,134 @@
|
|||
# Usage:
|
||||
# tcp_close [-q] [ -a | session ... ]
|
||||
# -a means all sessions.
|
||||
# -n means don't close a fake session's fd.
|
||||
# -q means quiet.
|
||||
#
|
||||
# Accepts the -s and -l arguments for consistenty with other functions,
|
||||
# but there is no particular gain in using them
|
||||
emulate -L zsh
|
||||
setopt extendedglob cbases
|
||||
|
||||
local all quiet opt alias noclose
|
||||
local -a sessnames
|
||||
|
||||
while getopts "aql:ns:" opt; do
|
||||
case $opt in
|
||||
(a) all=1
|
||||
;;
|
||||
(q) quiet=1
|
||||
;;
|
||||
(l) sessnames+=(${(s.,.)OPTARG})
|
||||
;;
|
||||
(n) noclose=1
|
||||
;;
|
||||
(s) sessnames+=($OPTARG)
|
||||
;;
|
||||
(*) return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1))
|
||||
|
||||
if [[ -n $all ]]; then
|
||||
if (( $# )); then
|
||||
print "Usage: $0 [ -q ] [ -a | [ session ... ] ]" >&2
|
||||
return 1
|
||||
fi
|
||||
sessnames=(${(k)tcp_by_name})
|
||||
if (( ! ${#sessnames} )); then
|
||||
[[ -z $quiet ]] && print "No TCP sessions open." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sessnames+=($*)
|
||||
|
||||
if (( ! ${#sessnames} )); then
|
||||
sessnames+=($TCP_SESS)
|
||||
fi
|
||||
|
||||
if (( ! ${#sessnames} )); then
|
||||
[[ -z $quiet ]] && print "No current TCP session." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tcp_sess fd
|
||||
integer stat curstat
|
||||
|
||||
# Check to see if the fd is opened for a TCP session, or was opened
|
||||
# to a pre-existing fd. We could remember this from tcp_open.
|
||||
local -A ztcp_fds
|
||||
local line match mbegin mend
|
||||
|
||||
if zmodload -e zsh/net/tcp; then
|
||||
ztcp | while read line; do
|
||||
if [[ $line = (#b)*fd\ ([0-9]##) ]]; then
|
||||
ztcp_fds[$match[1]]=1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for tcp_sess in $sessnames; do
|
||||
curstat=0
|
||||
fd=${tcp_by_name[$tcp_sess]}
|
||||
if [[ -z $fd ]]; then
|
||||
print "No TCP session $tcp_sess!" >&2
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
# We need the base name if this is an alias.
|
||||
tcp_sess=${tcp_by_fd[$fd]}
|
||||
if [[ -z $tcp_sess ]]; then
|
||||
if [[ -z $quiet ]]; then
|
||||
print "Aaargh! Session for fd $fd has disappeared!" >&2
|
||||
fi
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ${+tcp_aliases} -ne 0 && -n ${tcp_aliases[$fd]} ]]; then
|
||||
for alias in ${=tcp_aliases[$fd]}; do
|
||||
if (( ${+functions[tcp_on_unalias]} )); then
|
||||
tcp_on_unalias $alias $fd
|
||||
fi
|
||||
unset "tcp_by_name[$alias]"
|
||||
done
|
||||
unset "tcp_aliases[$fd]"
|
||||
fi
|
||||
|
||||
# Don't return just because the zle handler couldn't be uninstalled...
|
||||
if [[ -o zle ]]; then
|
||||
zle -F $fd || print "[Ignoring...]" >&2
|
||||
fi
|
||||
|
||||
if [[ -n $ztcp_fds[$fd] ]]; then
|
||||
# It's a ztcp session.
|
||||
if ! ztcp -c $fd; then
|
||||
stat=1
|
||||
curstat=1
|
||||
fi
|
||||
elif [[ -z $noclose ]]; then
|
||||
# It's not, just close it normally.
|
||||
# Careful: syntax for closing fd's is quite strict.
|
||||
if [[ ${#fd} -gt 1 ]]; then
|
||||
[[ -z $quiet ]] && print "Can't close fd $fd; will leave open." >&2
|
||||
else
|
||||
eval "exec $fd>&-"
|
||||
fi
|
||||
fi
|
||||
|
||||
unset "tcp_by_name[$tcp_sess]"
|
||||
unset "tcp_by_fd[$fd]"
|
||||
if [[ -z $quiet && $curstat -eq 0 ]]; then
|
||||
print "Session $tcp_sess successfully closed."
|
||||
fi
|
||||
[[ $tcp_sess = $TCP_SESS ]] && unset TCP_SESS
|
||||
|
||||
if (( ${+functions[tcp_on_close]} )); then
|
||||
tcp_on_close $tcp_sess $fd
|
||||
fi
|
||||
done
|
||||
|
||||
return $stat
|
3
Functions/TCP/tcp_command
Normal file
3
Functions/TCP/tcp_command
Normal file
|
@ -0,0 +1,3 @@
|
|||
tcp_send $* || return 1
|
||||
tcp_read -d -t ${TCP_TIMEOUT:=0.3}
|
||||
return 0
|
115
Functions/TCP/tcp_expect
Normal file
115
Functions/TCP/tcp_expect
Normal file
|
@ -0,0 +1,115 @@
|
|||
# Expect one of a series of regular expressions from $TCP_SESS.
|
||||
# Can make backreferences to be handled by $match. Returns 0 for
|
||||
# successful match, 1 for error, 2 for timeout.
|
||||
#
|
||||
# This function has no facility for conditionally calling code based
|
||||
# the regular expression found. This should be done in the calling code
|
||||
# by testing $TCP_LINE, which contains the line which matched the
|
||||
# regular expression. The complete set of lines read while waiting for
|
||||
# this line is available in the array $tcp_expect_lines (including $TCP_LINE
|
||||
# itself which will be the final element). Alternatively, use -p pind
|
||||
# which sets $pind to the index of the pattern which matched. It
|
||||
# will be set to 0 otherwise.
|
||||
#
|
||||
# Many of the options are passed straight down to tcp_read.
|
||||
#
|
||||
# Options:
|
||||
# -a Run tcp_expect across all sessions; the first pattern matched
|
||||
# from any session is used. The TCP output prompt can be
|
||||
# used to decide which session matched.
|
||||
# -l list
|
||||
# Comma-separated list of sessions as for tcp_read.
|
||||
# -p pv If the Nth of a series of patterns matches, set the parameter
|
||||
# whose name is given by $pv to N; in the case of a timeout,
|
||||
# set it to -1; otherwise (unless the function exited prematurely),
|
||||
# set it to 0.
|
||||
# To avoid namespace clashes, the parameter's name must
|
||||
# not begin with `_expect'.
|
||||
# -q Quiet, passed down to tcp_read. Bad option and argument
|
||||
# usage is always reported.
|
||||
# -s sess
|
||||
# Expect from session sess. May be repeated for multiple sessions.
|
||||
# -t to Timeout in seconds (may be floating point) per read operation.
|
||||
# tcp_expect will only time out if every read operation takes longer
|
||||
# than to
|
||||
# -T TO Overall timeout; tcp_expect will time out if the overall operation
|
||||
# takes longer than this many seconds.
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
# Get extra accuracy by making SECONDS floating point locally
|
||||
typeset -F SECONDS
|
||||
|
||||
# Variables are all named _expect_* to avoid problems with the -p param.
|
||||
local _expect_opt _expect_pvar
|
||||
local -a _expect_read_args
|
||||
float _expect_to1 _expect_to_all _expect_to _expect_new_to
|
||||
integer _expect_i _expect_stat
|
||||
|
||||
while getopts "al:p:qs:t:T:" _expect_opt; do
|
||||
case $_expect_opt in
|
||||
(a) _expect_read_args+=(-a)
|
||||
;;
|
||||
(l) _expect_read_args+=(-l $OPTARG)
|
||||
;;
|
||||
(p) _expect_pvar=$OPTARG
|
||||
if [[ $_expect_pvar != [a-zA-Z_][a-zA-Z_0-9]# ]]; then
|
||||
print "invalid parameter name: $_expect_pvar" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ $_expect_pvar = _expect* ]]; then
|
||||
print "$0: parameter names staring \`_expect' are reserved."
|
||||
return 1
|
||||
fi
|
||||
eval "$_expect_pvar=0"
|
||||
;;
|
||||
(q) _expect_read_args+=(-q)
|
||||
;;
|
||||
(s) _expect_read_args+=(-s $OPTARG)
|
||||
;;
|
||||
(t) _expect_to1=$OPTARG
|
||||
;;
|
||||
(T) _expect_to_all=$(( SECONDS + $OPTARG ))
|
||||
;;
|
||||
(\?) return 1
|
||||
;;
|
||||
(*) print Unhandled option $_expect_opt, complain >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
tcp_expect_lines=()
|
||||
while true; do
|
||||
if (( _expect_to_all || _expect_to1 )); then
|
||||
_expect_to=0
|
||||
(( _expect_to1 )) && (( _expect_to = _expect_to1 ))
|
||||
if (( _expect_to_all )); then
|
||||
# overall timeout, see if it has already triggered
|
||||
if (( (_expect_new_to = (_expect_to_all - SECONDS)) <= 0 )); then
|
||||
[[ -n $_expect_pvar ]] && eval "$_expect_pvar=-1"
|
||||
return 2
|
||||
fi
|
||||
if (( _expect_to <= 0 || _expect_new_to < _expect_to )); then
|
||||
_expect_to=$_expect_new_to
|
||||
fi
|
||||
fi
|
||||
tcp_read $_expect_read_args -t $_expect_to
|
||||
_expect_stat=$?
|
||||
else
|
||||
tcp_read $_expect_read_args -b
|
||||
_expect_stat=$?
|
||||
fi
|
||||
if (( _expect_stat )); then
|
||||
[[ -n $_expect_pvar ]] && eval "$_expect_pvar=-1"
|
||||
return $_expect_stat
|
||||
fi
|
||||
tcp_expect_lines+=($TCP_LINE)
|
||||
for (( _expect_i = 1; _expect_i <= $#; _expect_i++ )); do
|
||||
if [[ "$TCP_LINE" = ${~argv[_expect_i]} ]]; then
|
||||
[[ -n $_expect_pvar ]] && eval "$_expect_pvar=\$_expect_i"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
35
Functions/TCP/tcp_fd_handler
Normal file
35
Functions/TCP/tcp_fd_handler
Normal file
|
@ -0,0 +1,35 @@
|
|||
local line name=${tcp_by_fd[$1]}
|
||||
if [[ -n $name ]]
|
||||
then
|
||||
local TCP_INVALIDATE_ZLE
|
||||
if (( $# > 2 )); then
|
||||
zle -I
|
||||
## debugging only
|
||||
# print "Flags on the play:" ${argv[3,-1]}
|
||||
else
|
||||
TCP_INVALIDATE_ZLE=1
|
||||
fi
|
||||
if ! tcp_read -d -u $1; then
|
||||
[[ -n $TCP_INVALIDATE_ZLE ]] && zle -I
|
||||
print "[TCP fd $1 (session $name) gone awol; removing from poll list]" >& 2
|
||||
zle -F $1
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
zle -I
|
||||
# Handle fds not in the TCP set similarly.
|
||||
# This does the drain thing, to try and get as much data out as possible.
|
||||
if ! read line <&$1; then
|
||||
print "[Reading on $1 failed; removing from poll list]" >& 2
|
||||
zle -F $1
|
||||
return 1
|
||||
fi
|
||||
line="fd$1:$line"
|
||||
local newline
|
||||
while read -t newline <&$1; do
|
||||
line="${line}
|
||||
fd$1:$newline"
|
||||
done
|
||||
fi
|
||||
print -r - $line
|
94
Functions/TCP/tcp_log
Normal file
94
Functions/TCP/tcp_log
Normal file
|
@ -0,0 +1,94 @@
|
|||
# Log TCP output.
|
||||
#
|
||||
# Argument: Output filename.
|
||||
#
|
||||
# Options:
|
||||
# -a Append. Otherwise the existing file is truncated without warning.
|
||||
# (N.B.: even if logging was already active to it!)
|
||||
# -s Per-session logs. Output to <filename>1, <filename>2, etc.
|
||||
# -c Close logging.
|
||||
# -n/-N Turn off or on normal output; output only goes to the logfile, if
|
||||
# any. Otherwise, output also appears interactively. This
|
||||
# can be given with -c (or any other option), then no output
|
||||
# goes anywhere. However, input is still handled by the usual
|
||||
# mechanisms --- $tcp_lines and $TCP_LINE are still set, hence
|
||||
# tcp_expect still works. Equivalent to (un)setting TCP_SILENT.
|
||||
#
|
||||
# With no options and no arguments, print the current configuration.
|
||||
#
|
||||
# Per-session logs are raw output, otherwise $TCP_PROMPT is prepended
|
||||
# to each line.
|
||||
|
||||
emulate -L zsh
|
||||
setopt cbases extendedglob
|
||||
|
||||
local opt append sess close
|
||||
integer activity
|
||||
while getopts "ascnN" opt; do
|
||||
(( activity++ ))
|
||||
case $opt in
|
||||
# append to existing file
|
||||
a) append=1
|
||||
;;
|
||||
# per-session
|
||||
s) sess=1
|
||||
;;
|
||||
# close
|
||||
c) close=1
|
||||
;;
|
||||
# turn off interactive output
|
||||
n) TCP_SILENT=1
|
||||
;;
|
||||
# turn on interactive output
|
||||
N) unset TCP_SILENT
|
||||
;;
|
||||
# incorrect option
|
||||
\?) return 1
|
||||
;;
|
||||
# correct option I forgot about
|
||||
*) print "$0: option -$opt not handled, oops." >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1))
|
||||
|
||||
if [[ -n $close ]]; then
|
||||
if (( $# )); then
|
||||
print "$0: too many arguments for -c" >&2
|
||||
return 1
|
||||
fi
|
||||
unset TCP_LOG TCP_LOG_SESS
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( $# == 0 && ! activity )); then
|
||||
print "\
|
||||
Per-session log: ${TCP_LOG_SESS:-<none>}
|
||||
Overall log: ${TCP_LOG:-<none>}
|
||||
Silent? ${${TCP_SILENT:+yes}:-no}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( $# != 1 )); then
|
||||
print "$0: wrong number of arguments" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n $sess ]]; then
|
||||
TCP_LOG_SESS=$1
|
||||
if [[ -z $append ]]; then
|
||||
local sesslogs
|
||||
integer i
|
||||
sesslogs=(${TCP_LOG_SESS}*(N))
|
||||
# yes, i know i can do this with multios
|
||||
for (( i = 1; i <= $#sesslogs; i++ )); do
|
||||
: >$sesslogs[$i]
|
||||
done
|
||||
fi
|
||||
else
|
||||
TCP_LOG=$1
|
||||
[[ -z $append ]] && : >$TCP_LOG
|
||||
fi
|
||||
|
||||
return 0
|
197
Functions/TCP/tcp_open
Normal file
197
Functions/TCP/tcp_open
Normal file
|
@ -0,0 +1,197 @@
|
|||
# Open a TCP session, add it to the list, handle it with zle if that's running.
|
||||
# Unless using -a, -f, -l or -s, first two arguments are host and port.
|
||||
#
|
||||
# Remaining argument, if any, is the name of the session, which mustn't
|
||||
# clash with an existing one. If none is given, the number of the
|
||||
# connection is used (i.e. first connection is 1, etc.), or the first
|
||||
# available integer if that is already in use.
|
||||
#
|
||||
# Session names, whether provided on the command line or in the
|
||||
# .ztcp_sessions file should not be `clever'. A clever name is one
|
||||
# with characters that won't work. This includes whitespace and an
|
||||
# inconsistent set of punctuation characters. If in doubt, stick
|
||||
# to alphanumeric, underscore and non-initial hyphen.
|
||||
#
|
||||
# -a fd Accept a connection on fd and make that the session.
|
||||
# This will block until a successful incoming connection is received.
|
||||
#
|
||||
# fd is probably a value returned by ztcp -l; no front-end
|
||||
# is currently provided for that but it should simply be
|
||||
# a matter of calling `ztcp -l port' and storing $REPLY, then
|
||||
# closing the listened port with `ztcp -c $stored_fd'.
|
||||
#
|
||||
# -f fd `Fake' tcp connection on the given file descriptor. This
|
||||
# could be, for example, a file descriptor already opened to
|
||||
# a named pipe. It should not be a regular file, however.
|
||||
# Note that it is not a good idea for two different sessions
|
||||
# to be attempting to read from the same named pipe, so if
|
||||
# both ends of the pipe are to be handled by zsh, at least
|
||||
# one should use the `-z' option.
|
||||
#
|
||||
# -l sesslist
|
||||
# -s sessname
|
||||
# Open by session name or comma separated list; either may
|
||||
# be repeated as many times as necessary. The session must be
|
||||
# listed in the file ${ZDOTDIR:-$HOME}/.ztcp_sessions. Lines in
|
||||
# this file look exactly like a tcp_open command line except the
|
||||
# session name is at the start, for example
|
||||
# sess1 pwspc 2811
|
||||
# has the effect of
|
||||
# tcp_open pwspc 2811 sess1
|
||||
# Remaining arguments (other than options) to tcp_open are
|
||||
# not allowed. Options in .ztcp_sessions are not handled.
|
||||
# The file must be edited by hand.
|
||||
#
|
||||
# -z Don't install a zle handler for reading on the file descriptor.
|
||||
# Otherwise, if zle is enabled, the file descriptor will
|
||||
# be tested while at the shell prompt and any input automatically
|
||||
# printed in the same way as job control notification.
|
||||
#
|
||||
# If a session is successfully opened, and if the function `tcp_on_open'
|
||||
# exists, it is run with the arguments session_name, session_fd.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob cbases
|
||||
|
||||
zmodload -i zsh/net/tcp || return 1
|
||||
autoload -U zgprintf 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
|
||||
|
||||
local opt accept fake nozle sessfile sess quiet
|
||||
local -a sessnames sessargs
|
||||
integer stat
|
||||
|
||||
while getopts "a:f:l:qs:z" opt; do
|
||||
case $opt in
|
||||
(a) accept=$OPTARG
|
||||
if [[ $accept != [[:digit:]]## ]]; then
|
||||
print "option -a takes a file descriptor" >&2
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
(f) fake=$OPTARG
|
||||
if [[ $fake != [[:digit:]]## ]]; then
|
||||
print "option -f takes a file descriptor" >&2
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
(l) sessnames+=(${(s.,.)OPTARG})
|
||||
;;
|
||||
(q) quiet=1
|
||||
;;
|
||||
(s) sessnames+=($OPTARG)
|
||||
;;
|
||||
(z) nozle=1
|
||||
;;
|
||||
(*) return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
(( ${+tcp_by_fd} )) || typeset -gA tcp_by_fd
|
||||
(( ${+tcp_by_name} )) || typeset -gA tcp_by_name
|
||||
typeset -A sessassoc
|
||||
|
||||
if (( ${#sessnames} )); then
|
||||
if [[ $# -ne 0 || -n $accept || -n $fake ]]; then
|
||||
print "Incompatible arguments with \`-s' option." >&2
|
||||
return 1
|
||||
fi
|
||||
for sess in ${sessnames}; do
|
||||
sessassoc[$sess]=
|
||||
done
|
||||
|
||||
sessfile=${ZDOTDIR:-$HOME}/.ztcp_sessions
|
||||
if [[ ! -r $sessfile ]]; then
|
||||
print "No session file: $sessfile" >&2
|
||||
return 1
|
||||
fi
|
||||
while read -A sessargs; do
|
||||
[[ ${sessargs[1]} = '#'* ]] && continue
|
||||
if (( ${+sessassoc[${sessargs[1]}]} )); then
|
||||
sessassoc[${sessargs[1]}]="${sessargs[2,-1]}"
|
||||
fi
|
||||
done < $sessfile
|
||||
for sess in ${sessnames}; do
|
||||
if [[ -z $sessassoc[$sess] ]]; then
|
||||
print "Couldn't find session $sess in $sessfile." >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
if [[ -z $accept && -z $fake ]]; then
|
||||
if (( $# < 2 )); then
|
||||
set -- wrong number of arguments
|
||||
else
|
||||
host=$1 port=$2
|
||||
shift $(( $# > 1 ? 2 : 1 ))
|
||||
fi
|
||||
fi
|
||||
if [[ -n $1 ]]; then
|
||||
sessnames=($1)
|
||||
shift
|
||||
else
|
||||
sessnames=($(( ${#tcp_by_fd} + 1 )))
|
||||
while [[ -n $tcp_by_name[$sessnames[1]] ]]; do
|
||||
(( sessnames[1]++ ))
|
||||
done
|
||||
fi
|
||||
sessassoc[$sessnames[1]]="$host $port"
|
||||
fi
|
||||
|
||||
if (( $# )); then
|
||||
print "Usage: $0 [-z] [-a fd | -f fd | host port [ session ] ]
|
||||
$0 [-z] [ -s session | -l sesslist ] ..." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local REPLY fd
|
||||
for sess in $sessnames; do
|
||||
if [[ -n $tcp_by_name[$sess] ]]; then
|
||||
print "Session \`$sess' already exists." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sessargs=()
|
||||
if [[ -n $fake ]]; then
|
||||
fd=$fake;
|
||||
else
|
||||
if [[ -n $accept ]]; then
|
||||
ztcp -a $accept || return 1
|
||||
else
|
||||
sessargs=(${=sessassoc[$sess]})
|
||||
ztcp $sessargs || return 1
|
||||
fi
|
||||
fd=$REPLY
|
||||
fi
|
||||
|
||||
tcp_by_fd[$fd]=$sess
|
||||
tcp_by_name[$sess]=$fd
|
||||
|
||||
[[ -o zle && -z $nozle ]] && zle -F $fd tcp_fd_handler
|
||||
if [[ -z $quiet ]]; then
|
||||
if (( ${#sessargs} )); then
|
||||
print "Session $sess" \
|
||||
"(host $sessargs[1], port $sessargs[2] fd $fd) opened OK."
|
||||
else
|
||||
print "Session $sess (fd $fd) opened OK."
|
||||
fi
|
||||
fi
|
||||
|
||||
# needed for new completion system, so I'm not too sanguine
|
||||
# about requiring this here...
|
||||
if zmodload -i zsh/parameter; then
|
||||
if (( ${+functions[tcp_on_open]} )); then
|
||||
tcp_on_open $sess $fd
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z $TCP_SESS ]]; then
|
||||
[[ -z $quiet ]] && print "Setting default TCP session $sessnames[1]"
|
||||
TCP_SESS=$sessnames[1]
|
||||
fi
|
||||
|
||||
return $stat
|
65
Functions/TCP/tcp_output
Normal file
65
Functions/TCP/tcp_output
Normal file
|
@ -0,0 +1,65 @@
|
|||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
local opt tprompt sess read_fd tpat quiet
|
||||
|
||||
while getopts "F:P:qS:" opt; do
|
||||
case $opt in
|
||||
(F) read_fd=$OPTARG
|
||||
;;
|
||||
(P) tprompt=$OPTARG
|
||||
;;
|
||||
(q) quiet=1
|
||||
;;
|
||||
(S) sess=$OPTARG
|
||||
;;
|
||||
(*) [[ $opt != \? ]] && print -r "Can't handle option $opt" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
# Per-session logs don't have the session discriminator in front.
|
||||
if [[ -n $TCP_LOG_SESS ]]; then
|
||||
print -r -- "$*" >>${TCP_LOG_SESS}.$sess
|
||||
fi
|
||||
# Always add the TCP prompt. We used only to do this with
|
||||
# multiple sessions, but it seems always to be useful to know
|
||||
# where data is coming from; also, it allows more predictable
|
||||
# behaviour in tcp_expect.
|
||||
if [[ -n $tprompt ]]; then
|
||||
zgprintf -R -%s=$sess -%f=$read_fd -- $tprompt
|
||||
# We will pass this back up.
|
||||
REPLY="$REPLY$*"
|
||||
else
|
||||
REPLY="$*"
|
||||
fi
|
||||
if [[ -n $TCP_LOG ]]; then
|
||||
print -r -- $REPLY >>${TCP_LOG}
|
||||
fi
|
||||
|
||||
if [[ -z $quiet ]]; then
|
||||
local skip=
|
||||
if [[ ${#tcp_filter} -ne 0 ]]; then
|
||||
# Allow tcp_filter to be an associative array, though
|
||||
# it doesn't *need* to be.
|
||||
for tpat in ${(v)tcp_filter}; do
|
||||
[[ $REPLY = ${~tpat} ]] && skip=1 && break
|
||||
done
|
||||
fi
|
||||
if [[ -z $skip ]]; then
|
||||
# Check flag passed down probably from tcp_fd_handler:
|
||||
# if we have output, we are in zle and need to fix the display first.
|
||||
# (The shell is supposed to be smart enough that you can replace
|
||||
# all the following with
|
||||
# [[ -o zle ]] && zle -I
|
||||
# but I haven't dared try it yet.)
|
||||
if [[ -n $TCP_INVALIDATE_ZLE ]]; then
|
||||
zle -I
|
||||
# Only do this the first time.
|
||||
unset TCP_INVALIDATE_ZLE
|
||||
fi
|
||||
print -r -- $REPLY
|
||||
fi
|
||||
fi
|
31
Functions/TCP/tcp_proxy
Normal file
31
Functions/TCP/tcp_proxy
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Listen on the given port and for every connection, start a new
|
||||
# command (defaults to $SHELL) in the background on the accepted fd.
|
||||
# WARNING: this can leave your host open to the universe. For use
|
||||
# in a restricted fashion on a secure network.
|
||||
#
|
||||
# Remote logins are much more efficient...
|
||||
|
||||
local TCP_LISTEN_FD
|
||||
trap '[[ -n $TCP_LISTEN_FD ]] && ztcp -c $TCP_LISTEN_FD; return 1' \
|
||||
HUP INT TERM EXIT PIPE
|
||||
|
||||
if [[ $1 != <-> ]]; then
|
||||
print "Usage: $0 port [cmd args... ]" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
integer port=$1
|
||||
shift
|
||||
ztcp -l $port || return 1
|
||||
TCP_LISTEN_FD=$REPLY
|
||||
|
||||
(( $# )) || set -- ${SHELL:-zsh}
|
||||
local cmd=$1
|
||||
shift
|
||||
|
||||
while ztcp -a $TCP_LISTEN_FD; do
|
||||
# hack to expand aliases without screwing up arguments
|
||||
eval $cmd '$* <&$REPLY >&$REPLY 2>&$REPLY &'
|
||||
# Close the session fd; we don't need it here any more.
|
||||
ztcp -c $REPLY
|
||||
done
|
207
Functions/TCP/tcp_read
Normal file
207
Functions/TCP/tcp_read
Normal file
|
@ -0,0 +1,207 @@
|
|||
# Helper function for reading input from a TCP connection.
|
||||
# Actually, the input doesn't need to be a TCP connection at all, it
|
||||
# is simply an input file descriptor. However, it must be contained
|
||||
# in ${tcp_by_fd[$TCP_SESS]}. This is set set by tcp_open, but may be
|
||||
# set by hand. (Note, however, the blocking/timeout behaviour is usually
|
||||
# not implemented for reading from regular files.)
|
||||
#
|
||||
# The default behaviour is simply to read any single available line from
|
||||
# the input fd and print it. If a line is read, it is stored in the
|
||||
# parameter $TCP_LINE; this always contains the last line successfully
|
||||
# read. Any chunk of lines read in are stored in the array $tcp_lines;
|
||||
# this always contains a complete list of all lines read in by a single
|
||||
# execution of this function and hence may be empty. The fd corresponding
|
||||
# to $TCP_LINE is stored in $TCP_LINE_FD (this can be turned into a
|
||||
# session by looking up in $tcp_by_fd).
|
||||
#
|
||||
# Printed lines are preceded by $TCP_PROMPT. This may contain two
|
||||
# percent escapes: %s for the current session, %f for the current file
|
||||
# descriptor. The default is `T[%s]:'. The prompt is not printed
|
||||
# to per-session logs where the source is unambiguous.
|
||||
#
|
||||
# The function returns 0 if a read succeeded, even if (using -d) a
|
||||
# subsequent read failed.
|
||||
#
|
||||
# The behaviour is modified by the following options.
|
||||
#
|
||||
# -a Read from all fds, not just the one given by TCP_SESS.
|
||||
#
|
||||
# -b The first read blocks until some data is available for reading.
|
||||
#
|
||||
# -d Drain all pending input; loop until no data is available.
|
||||
#
|
||||
# -l sess1,sess2,...
|
||||
# Gives a list of sessions to read on. Equivalent to
|
||||
# -u ${tcp_by_name[sess1]} -u ${tcp_by_name[sess2]} ...
|
||||
# Multiple -l options also work.
|
||||
#
|
||||
# -q Quiet; if $TCP_SESS is not set, just return 1, but don't print
|
||||
# an error message.
|
||||
#
|
||||
# -s sess
|
||||
# Gives a single session; the option may be repeated.
|
||||
#
|
||||
# -t TO On each read (the only read unless -d was also given), time out
|
||||
# if nothing was available after TO seconds (may be floating point).
|
||||
# Otherwise, the function will return immediately when no data is
|
||||
# available.
|
||||
#
|
||||
# If combined with -b, the function will always wait for the
|
||||
# first data to become available; hence this is not useful unless
|
||||
# -d is specified along with -b, in which case the timeout applies
|
||||
# to data after the first line.
|
||||
# -u fd Read from fd instead of the default session; may be repeated for
|
||||
# multiple sessions. Can be a comma-separated list, too.
|
||||
# -T TO This sets an overall timeout, again in seconds.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob cbases
|
||||
# set -x
|
||||
|
||||
zmodload -i zsh/mathfunc
|
||||
|
||||
local opt drain line quiet block read_fd all sess
|
||||
local -A read_fds
|
||||
read_fds=()
|
||||
float timeout timeout_all endtime
|
||||
integer stat
|
||||
|
||||
while getopts "abdl:qs:t:T:u:" opt; do
|
||||
case $opt in
|
||||
# Read all sessions.
|
||||
(a) all=1
|
||||
;;
|
||||
# Block until we receive something.
|
||||
(b) block=1
|
||||
;;
|
||||
# Drain all pending input.
|
||||
(d) drain=1
|
||||
;;
|
||||
(l) for sess in ${(s.,.)OPTARG}; do
|
||||
read_fd=${tcp_by_name[$sess]}
|
||||
if [[ -z $read_fd ]]; then
|
||||
print "$0: no such session: $sess" >&2
|
||||
return 1
|
||||
fi
|
||||
read_fds[$read_fd]=1
|
||||
done
|
||||
;;
|
||||
|
||||
# Don't print an error mesage if there is no TCP connection,
|
||||
# just return 1.
|
||||
(q) quiet=1
|
||||
;;
|
||||
# Add a single session to the list
|
||||
(s) read_fd=${tcp_by_name[$OPTARG]}
|
||||
if [[ -z $read_fd ]]; then
|
||||
print "$0: no such session: $sess" >&2
|
||||
return 1
|
||||
fi
|
||||
read_fds[$read_fd]=1
|
||||
;;
|
||||
# Per-read timeout: wait this many seconds before
|
||||
# each read.
|
||||
(t) timeout=$OPTARG
|
||||
[[ -n $TCP_READ_DEBUG ]] && print "Timeout per-operations is $timeout" >&2
|
||||
;;
|
||||
# Overall timeout: return after this many seconds.
|
||||
(T) timeout_all=$OPTARG
|
||||
;;
|
||||
# Read from given fd(s).
|
||||
(u) for read_fd in ${(s.,.)OPTARG}; do
|
||||
if [[ $read_fd != (0x[[:xdigit:]]##|[[:digit:]]##) ]]; then
|
||||
print "Bad fd in $OPTARG" >&2
|
||||
return 1
|
||||
fi
|
||||
read_fds[$((read_fd))]=1
|
||||
done
|
||||
;;
|
||||
(*) [[ $opt != \? ]] && print Unhandled option, complain: $opt >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n $all ]]; then
|
||||
read_fds=(${(kv)tcp_by_fd})
|
||||
elif (( ! $#read_fds )); then
|
||||
if [[ -z $TCP_SESS ]]; then
|
||||
[[ -z $quiet ]] && print "No tcp connection open." >&2
|
||||
return 1
|
||||
elif [[ -z $tcp_by_name[$TCP_SESS] ]]; then
|
||||
print "TCP session $TCP_SESS has gorn!" >&2
|
||||
return 1
|
||||
fi
|
||||
read_fds[$tcp_by_name[$TCP_SESS]]=1
|
||||
fi
|
||||
|
||||
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 (( timeout_all )); then
|
||||
(( endtime = SECONDS + timeout_all ))
|
||||
fi
|
||||
|
||||
zmodload -i zsh/zselect
|
||||
|
||||
if [[ -n $block ]]; then
|
||||
if (( timeout_all )); then
|
||||
# zselect -t uses 100ths of a second
|
||||
zselect -t $(( int(100*timeout_all + 0.5) )) ${(k)read_fds} ||
|
||||
return $helper_stat
|
||||
else
|
||||
zselect ${(k)read_fds} || return $helper_stat
|
||||
fi
|
||||
fi
|
||||
|
||||
while (( ${#read_fds} )); do
|
||||
if [[ -n $block ]]; then
|
||||
# We already have data waiting this time through.
|
||||
unset block
|
||||
else
|
||||
if (( timeout_all )); then
|
||||
(( (newtimeout = endtime - SECONDS) <= 0 )) && return 2
|
||||
if (( ! timeout || newtimeout < timeout )); then
|
||||
(( timeout = newtimeout ))
|
||||
fi
|
||||
fi
|
||||
if (( timeout )); then
|
||||
if [[ -n $TCP_READ_DEBUG ]]; then
|
||||
print "[tcp_read: selecting timeout $timeout on ${(k)read_fds}]" >&2
|
||||
fi
|
||||
zselect -t $(( int(timeout*100 + 0.5) )) ${(k)read_fds} ||
|
||||
return $helper_stat
|
||||
else
|
||||
if [[ -n $TCP_READ_DEBUG ]]; then
|
||||
print "[tcp_read: selecting no timeout on ${(k)read_fds}]" >&2
|
||||
fi
|
||||
zselect -t 0 ${(k)read_fds} || return $helper_stat
|
||||
fi
|
||||
fi
|
||||
if [[ -n $TCP_READ_DEBUG ]]; then
|
||||
print "[tcp_read: returned fds ${reply}]" >&2
|
||||
fi
|
||||
for read_fd in ${reply[2,-1]}; do
|
||||
if ! read -r line <&$read_fd; then
|
||||
unset "read_fds[$read_fd]"
|
||||
stat=1
|
||||
continue
|
||||
fi
|
||||
|
||||
helper_stat=0
|
||||
sess=${tcp_by_fd[$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
|
||||
# Only handle one line from one device at a time unless draining.
|
||||
[[ -z $drain ]] && return $stat
|
||||
done
|
||||
done
|
||||
|
||||
return $stat
|
43
Functions/TCP/tcp_rename
Normal file
43
Functions/TCP/tcp_rename
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Rename session OLD (defaults to current session) to session NEW.
|
||||
# Does not handle aliases; use tcp_alias for all alias redefinitions.
|
||||
|
||||
local old new
|
||||
|
||||
if (( $# == 1 )); then
|
||||
old=$TCP_SESS
|
||||
new=$1
|
||||
elif (( $# == 2 )); then
|
||||
old=$1
|
||||
new=$2
|
||||
else
|
||||
print "Usage: $0 OLD NEW" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local fd=$tcp_by_name[$old]
|
||||
if [[ -z $fd ]]; then
|
||||
print "No such session: $old" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ -n $tcp_by_name[$new] ]]; then
|
||||
print "Session $new already exists." >&2
|
||||
return 1
|
||||
fi
|
||||
# Can't rename an alias
|
||||
if [[ $tcp_by_fd[$fd] != $old ]]; then
|
||||
print "Use tcp_alias to redefine an alias." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
tcp_by_name[$new]=$fd
|
||||
unset "tcp_by_name[$old]"
|
||||
|
||||
tcp_by_fd[$fd]=$new
|
||||
|
||||
[[ $TCP_SESS = $old ]] && TCP_SESS=$new
|
||||
|
||||
if zmodload -i zsh/parameter; then
|
||||
if (( ${+functions[tcp_on_rename]} )); then
|
||||
tcp_on_rename $new $fd $old
|
||||
fi
|
||||
fi
|
67
Functions/TCP/tcp_send
Normal file
67
Functions/TCP/tcp_send
Normal file
|
@ -0,0 +1,67 @@
|
|||
emulate -L zsh
|
||||
setopt extendedglob cbases
|
||||
|
||||
local opt quiet all sess fd nonewline
|
||||
local -a sessions write_fds
|
||||
|
||||
while getopts "al:nqs:" opt; do
|
||||
case $opt in
|
||||
(a) all=1
|
||||
;;
|
||||
(n) nonewline=-n
|
||||
;;
|
||||
(q) quiet=1
|
||||
;;
|
||||
(l) for sess in ${(s.,.)OPTARG}; do
|
||||
if [[ -z ${tcp_by_name[$sess]} ]]; then
|
||||
print "$0: no such session: $sess" >&2
|
||||
return 1
|
||||
fi
|
||||
sessions+=($sess)
|
||||
done
|
||||
;;
|
||||
(s) if [[ -z $tcp_by_name[$OPTARG] ]]; then
|
||||
print "No such session: $OPTARG" >&2
|
||||
return 1
|
||||
fi
|
||||
sessions+=($OPTARG)
|
||||
;;
|
||||
(*) [[ $opt != '?' ]] && print Unhandled option, complain: $opt >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
if [[ -n $all ]]; then
|
||||
sessions=(${(k)tcp_by_name})
|
||||
elif (( ! ${#sessions} )); then
|
||||
sessions=($TCP_SESS)
|
||||
fi
|
||||
if (( ! $#sessions )); then
|
||||
if [[ -z $quiet ]]; then
|
||||
print "No current TCP session open." >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Writing on a TCP connection closed by the remote end can cause SIGPIPE.
|
||||
# The following test is reasonably robust, though in principle we can
|
||||
# mistake a SIGPIPE owing to another fd. That doesn't seem like a big worry.
|
||||
# `emulate -L zsh' will already have set localtraps.
|
||||
local TCP_FD_CLOSED
|
||||
trap 'TCP_FD_CLOSED=1' PIPE
|
||||
|
||||
local TCP_SESS
|
||||
|
||||
for TCP_SESS in $sessions; do
|
||||
fd=${tcp_by_name[$TCP_SESS]}
|
||||
print $nonewline -r -- $* >&$fd
|
||||
if [[ $? -ne 0 || -n $TCP_FD_CLOSED ]]; then
|
||||
print "Session ${TCP_SESS}: fd $fd unusable." >&2
|
||||
unset TCP_FD_CLOSED
|
||||
fi
|
||||
if [[ -n $TCP_OUTPUT ]]; then
|
||||
tcp_output -P "$TCP_OUTPUT" -S $TCP_SESS -F $fd -q "${(j. .)*}"
|
||||
fi
|
||||
done
|
39
Functions/TCP/tcp_sess
Normal file
39
Functions/TCP/tcp_sess
Normal file
|
@ -0,0 +1,39 @@
|
|||
# try to disguise parameters from the eval'd command in case it's a function.
|
||||
integer __myfd=1
|
||||
|
||||
if [[ -n $1 ]]; then
|
||||
if [[ -z $tcp_by_name[$1] ]]; then
|
||||
print no such session: $1
|
||||
__myfd=2
|
||||
elif [[ -n $2 ]]; then
|
||||
local TCP_SESS=$1
|
||||
shift
|
||||
# A bit tricky: make sure the first argument gets re-evaluated,
|
||||
# so as to get aliases etc. to work, but make sure the remainder
|
||||
# don't, so as not to bugger up quoting. This ought to work the
|
||||
# vast majority of the time, anyway.
|
||||
local __cmd=$1
|
||||
shift
|
||||
eval $__cmd \$\*
|
||||
return
|
||||
else
|
||||
TCP_SESS=$1
|
||||
return 0;
|
||||
fi
|
||||
fi
|
||||
|
||||
# Print out the list of sessions, first the number, than the corresponding
|
||||
# file descriptor. The current session, if any, is marked with an asterisk.
|
||||
local cur name fd
|
||||
for name in ${(ko)tcp_by_name}; do
|
||||
fd=${tcp_by_name[$name]}
|
||||
# mark current session with an asterisk
|
||||
if [[ ${TCP_SESS} = $name ]]; then
|
||||
cur=" *"
|
||||
else
|
||||
cur=
|
||||
fi
|
||||
print "sess:$name; fd:$fd$cur" >&$__myfd
|
||||
done
|
||||
|
||||
return $(( __myfd - 1 ))
|
97
Functions/TCP/tcp_spam
Normal file
97
Functions/TCP/tcp_spam
Normal file
|
@ -0,0 +1,97 @@
|
|||
# SPAM is a registered trademark of Hormel Foods Corporation.
|
||||
#
|
||||
# -a all connections, override $tcp_spam_list and $tcp_no_spam_list.
|
||||
# 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.
|
||||
# -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).
|
||||
# -t transmit, send data to slave rather than executing command for eac
|
||||
# session.
|
||||
# -v verbose, list session being spammed in turn
|
||||
#
|
||||
# If the function tcp_on_spam is defined, it is called for each link
|
||||
# with the first argument set to the session name, and the remainder the
|
||||
# command line to be executed. If it sets the parameter REPLY to `done',
|
||||
# the command line will not then be executed by tcp_spam, else it will.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
local TCP_SESS cmd opt verbose reverse sesslist transmit all
|
||||
local match mbegin mend REPLY
|
||||
local -a sessions
|
||||
|
||||
while getopts "al:rtv" opt; do
|
||||
case $opt in
|
||||
(a) all=1
|
||||
;;
|
||||
(l) sessions+=(${(s.,.)OPTARG})
|
||||
;;
|
||||
(r) reverse=1
|
||||
;;
|
||||
(s) sessions+=($OPTARG)
|
||||
;;
|
||||
(t) transmit=-t
|
||||
;;
|
||||
(v) verbose=1
|
||||
;;
|
||||
(*) [[ $opt != '?' ]] && print "Option $opt not handled." >&2
|
||||
print "Sorry, spam's off." >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
local name
|
||||
if [[ -n $all ]]; then
|
||||
sessions=(${(ko)tcp_by_name})
|
||||
elif (( ! ${#sessions} )); then
|
||||
if (( ${#tcp_spam_list} )); then
|
||||
sessions=($tcp_spam_list)
|
||||
else
|
||||
sessions=(${(ko)tcp_by_name})
|
||||
fi
|
||||
if (( ${#tcp_no_spam_list} )); then
|
||||
for name in ${tcp_no_spam_list}; do
|
||||
sessions=(${sessions:#$name})
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n $reverse ]]; then
|
||||
local tmp
|
||||
integer i
|
||||
for (( i = 1; i <= ${#sessions}/2; i++ )); do
|
||||
tmp=${sessions[i]}
|
||||
sessions[i]=${sessions[-i]}
|
||||
sessions[-i]=$tmp
|
||||
done
|
||||
fi
|
||||
|
||||
if (( ! ${#sessions} )); then
|
||||
print "No connections to spam." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n $transmit ]]; then
|
||||
cmd=tcp_send
|
||||
else
|
||||
cmd=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
: ${TCP_PROMPT:=T[%s]:}
|
||||
|
||||
for TCP_SESS in $sessions; do
|
||||
REPLY=
|
||||
if (( ${+functions[tcp_on_spam]} )); then
|
||||
tcp_on_spam $TCP_SESS $cmd $*
|
||||
[[ $REPLY = done ]] && continue
|
||||
fi
|
||||
[[ -n $verbose ]] && zgprintf -R -%s=$TCP_SESS \
|
||||
-%f=${tcp_by_name[$TCP_SESS]} -- $TCP_PROMPT
|
||||
eval $cmd '$*'
|
||||
done
|
50
Functions/TCP/tcp_talk
Normal file
50
Functions/TCP/tcp_talk
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Make line editor input go straight to the current TCP session.
|
||||
# Returns when the string $TCP_TALK_ESCAPE (default :) is read on its own.
|
||||
# Otherwise, $TCP_TALK_ESCAPE followed by whitespace at the start of a line
|
||||
# is stripped off and the rest of the line passed to the shell.
|
||||
#
|
||||
# History is not currently handled, because this is difficult.
|
||||
|
||||
: ${TCP_TALK_ESCAPE:=:}
|
||||
|
||||
tcp-accept-line-or-exit() {
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
local match mbegin mend
|
||||
|
||||
if [[ $BUFFER = ${TCP_TALK_ESCAPE}[[:blank:]]#(#b)(*) ]]; then
|
||||
if [[ -z $match[1] ]]; then
|
||||
BUFFER=
|
||||
zle -A .accept-line accept-line
|
||||
PS1=$TCP_SAVE_PS1
|
||||
unset TCP_SAVE_PS1
|
||||
zle -I
|
||||
print '\r[Normal keyboard input restored]' >&2
|
||||
else
|
||||
BUFFER=$match[1]
|
||||
fi
|
||||
zle .accept-line
|
||||
else
|
||||
# BUGS: is deleted from the command line and doesn't appear in
|
||||
# the history.
|
||||
|
||||
# The following attempt to get the BUFFER into the history falls
|
||||
# foul of the fact that we need to accept the current line first.
|
||||
# But we don't actually want to accept the current line at all.
|
||||
# print -s -r - $BUFFER
|
||||
|
||||
# This is my function to send data over a TCP connection; replace
|
||||
# it with something else or nothing.
|
||||
tcp_send $BUFFER
|
||||
BUFFER=
|
||||
fi
|
||||
}
|
||||
|
||||
TCP_SAVE_PS1=${PS1##\[T*\]}
|
||||
if [[ -o prompt_subst ]]; then
|
||||
PS1="T[\$TCP_SESS]$TCP_SAVE_PS1"
|
||||
else
|
||||
PS1="[T]$TCP_SAVE_PS1"
|
||||
fi
|
||||
zle -N tcp-accept-line-or-exit
|
||||
zle -A tcp-accept-line-or-exit accept-line
|
11
Functions/TCP/tcp_wait
Normal file
11
Functions/TCP/tcp_wait
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Wait for given number of seconds, reading any data from
|
||||
# all TCP connections while doing so.
|
||||
|
||||
typeset -F SECONDS to end
|
||||
|
||||
(( to = $1, end = SECONDS + to ))
|
||||
while (( SECONDS < end )); do
|
||||
tcp_read -a -T $to
|
||||
(( to = end - SECONDS ))
|
||||
done
|
||||
return
|
70
Functions/TCP/zgprintf
Normal file
70
Functions/TCP/zgprintf
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Generalised printf.
|
||||
# Arguments of the form -%X=... give the output to be used with
|
||||
# the directive %x.
|
||||
#
|
||||
# -P indicates that any unhandled directives are to be
|
||||
# passed to printf. With this option, any %-escapes passed to printf
|
||||
# are assumed to consume exactly one argument from the command line.
|
||||
# Unused command line arguments are ignored. This is only minimally
|
||||
# implemented.
|
||||
#
|
||||
# -R indicates the value is to be put into REPLY rather than printed.
|
||||
#
|
||||
# -r indicates that print formatting (backslash escapes etc.) should
|
||||
# not be replied to the result. When using -R, no print formatting
|
||||
# is applied in any case.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
local opt printf fmt usereply match mbegin mend raw c
|
||||
typeset -A chars
|
||||
chars=(% %)
|
||||
|
||||
while getopts "%:PrR" opt; do
|
||||
case $opt in
|
||||
(%) if [[ $OPTARG != ?=* ]]; then
|
||||
print -r "Bad % option: should be -%${OPTARG[1]}=..." >&2
|
||||
return 1
|
||||
fi
|
||||
chars[${OPTARG[1]}]=${OPTARG[3,-1]}
|
||||
;;
|
||||
(P) printf=1
|
||||
;;
|
||||
(r) raw=-r
|
||||
;;
|
||||
(R) usereply=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
[[ -z $usereply ]] && local REPLY
|
||||
REPLY=
|
||||
|
||||
if (( $# )); then
|
||||
fmt=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
while [[ $fmt = (#b)([^%]#)%([-0-9.*]#?)(*) ]]; do
|
||||
REPLY+=$match[1]
|
||||
c=$match[2]
|
||||
fmt=$match[3]
|
||||
if [[ -n ${chars[$c]} ]]; then
|
||||
REPLY+=${chars[$c]}
|
||||
elif [[ -n $P ]]; then
|
||||
# hmmm, we need sprintf...
|
||||
# TODO: %ld etc.
|
||||
REPLY+=`printf "%$c" $1`
|
||||
(( $? )) && return 1
|
||||
shift
|
||||
else
|
||||
print -r "Format not handled: %$c" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
REPLY+=$fmt
|
||||
[[ -z $usereply ]] && print -n $raw - $REPLY
|
||||
return 0
|
Loading…
Add table
Add a link
Reference in a new issue