summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/comm
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/comm
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/comm')
-rw-r--r--tcllib/modules/comm/ChangeLog368
-rw-r--r--tcllib/modules/comm/comm.LICENSE48
-rw-r--r--tcllib/modules/comm/comm.man1230
-rw-r--r--tcllib/modules/comm/comm.n.html1067
-rw-r--r--tcllib/modules/comm/comm.pcx99
-rw-r--r--tcllib/modules/comm/comm.slaveboot42
-rw-r--r--tcllib/modules/comm/comm.tcl1818
-rw-r--r--tcllib/modules/comm/comm.test318
-rw-r--r--tcllib/modules/comm/comm_wire.man284
-rw-r--r--tcllib/modules/comm/pkgIndex.tcl2
10 files changed, 5276 insertions, 0 deletions
diff --git a/tcllib/modules/comm/ChangeLog b/tcllib/modules/comm/ChangeLog
new file mode 100644
index 0000000..afc2cff
--- /dev/null
+++ b/tcllib/modules/comm/ChangeLog
@@ -0,0 +1,368 @@
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2010-09-15 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl (::comm::commIncoming): [Bug 3066872]: Replaced
+ blocking gets call to read line of offered protocols by
+ non-blocking gets and additional event handling. The procedure
+ "commIncoming" is split into two.
+
+ * comm.tcl (::comm::Word0): [Bug 2972571]: Fixed misdetection
+ * comm.man: of quoted brace due to not handling \\ on its
+ * comm.test: own. Extended testsuite. Updated docs.
+
+ * pkgIndex.tcl: Bumped to version 4.6.2.
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2009-11-04 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl (::comm::commCollect): [Bug 2890743]. Replaced
+ * comm.man: lindex/lreplace with a procedure emulating lindex's
+ * pkgIndex.tcl: behaviour pre Tcl 8, i.e. it needs only the first
+ word to be a proper list element to parse it ouf the
+ buffer. Bumped package version to 4.6.1.
+
+2009-04-10 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.tcl: Added option -socketcmd enabling users to override how
+ * comm.man: a socket is opened. The envisioned main use is the
+ * pkgIndex.tcl: specification of tls::socket to secure the
+ * comm.pcx: communications. Version bumped to 4.6. Extended syntax
+ * comm.test: definitions for tclchecker, and extended testsuite.
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-06-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.pcx: New file. Syntax definitions for the public commands
+ of the comm package.
+
+2008-05-16 Andreas Kupries <andreask@activestate.com>
+
+ * comm_wire.man: Fixed the sectref argument order issues.
+
+2008-05-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm_wire.man: Updated to changes in doctools (sub)section
+ reference handling.
+
+2008-03-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm_wire.man: Changed 'require' information to show that this
+ manpage belongs to the documentation for the package 'comm'.
+
+2008-02-29 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl: Accepted Hemang's <hemanglavana@users.sourceforge.net>
+ * comm.man: patch for the [SF Tcllib Bug 1861565] he
+ * comm.test: reported. This changes the handling of 'port already
+ * pkgIndex.tcl: in use' errors to provide a clear
+ message. Testsuite was updated. Version bumped to 4.5.7.
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-08-21 Andreas Kupries <andreask@activestate.com>
+
+ * comm_wire.man: Fixed description of messages in the basic
+ message layer, and of EOL, per [SF Tcllib Bug 1739372] (by Lars
+ Hellstroem). General cleanup (spell checking).
+
+2007-08-20 Andreas Kupries <andreask@activestate.com>
+
+ * comm.test: Updated to require Tcl 8.3 (for snit).
+
+2007-08-15 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl (::comm::Vwait): Fixed uplevel which failed when used
+ * comm.man: with variable names containing spaces. Bumped the
+ * pkgIndex.tcl: package version to 4.5.6. Thanks to Bryan Oakley.
+
+2007-08-14 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl (::comm::CommRunHook): Fixed typo in variable name,
+ * comm.man: should be 'res', not 'result'. Bumped the package
+ * pkgIndex.tcl: version to 4.5.5. Thanks to Bryan Oakley.
+
+2007-08-09 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl (comm::comm_cmd_send): Replaced the 'after idle unset
+ result' with an immediate unset, saving the information into a
+ local variable. The 'after idle' can spill into a forked child
+ process if there is no event loop between its location and the
+ fork. This may bork the child if the next event loop is the
+ 'vwait' of comm's send a few lines above, and the child used the
+ same serial number for its next request. In that case the
+ parent's 'after idle unset' will delete the very array element
+ the child is waiting for, unlocking the vwait, causing it to
+ access a now missing array element, instead of the expected
+ result. Fix by JeffH, Analysis by AndreasK, bugfix actually done
+ before the analysis. This bug happened at Cisco.
+ * comm.man: Bumped the package version to 4.5.4.
+ * pkgIndex.tcl:
+
+2007-06-10 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.tcl: Bugfixes in the wrapper for 'update'.
+ * comm.man: Bumped the package version to 4.5.3.
+ * pkgIndex.tcl:
+
+2007-05-10 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl: Bugfix in the wrapper for 'update'. Bumped the
+ * comm.man: package version to 4.5.2.
+ * pkgIndex.tcl:
+
+2007-05-04 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl: Bugfixes in the handling of -interp for regular
+ * comm.man: scripts. The handling of the buffer was wrong for
+ * pkgIndex.tcl: scripts which are a single statement as
+ list. Fixed missing argument to new command commSendReply,
+ introduced by version 4.5. Affected debugging. Bumped package
+ version to 4.5.1.
+
+2007-05-01 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl: Added ability to asynchronously generate script
+ * comm.man: results. Enables proper handling of long-running
+ * comm.test: operations (like db queries) without blocking the
+ * comm.slaveboot: server, nor requiring nested eventloops.
+ * pkgIndex.tcl: Extended documentation, and testsuite. Version
+ bumped to 4.5. Now depending on snit, and Tcl 8.3.
+
+2007-03-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.man: Fixed all warnings due to use of now deprecated
+ * comm_wire.man: commands. Added a section about how to give
+ feedback.
+
+2006-11-29 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.tcl (commRunHook): Fixed double execution of the hook
+ script. Thanks to Will Duquette for the report.
+
+2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.9 ========================
+ *
+
+2006-09-19 Andreas Kupries <andreask@activestate.com>
+
+ * pkgIndex.tcl: Bumped to version 4.4. changes were extension
+ * comm.tcl: of the existing API.
+ * comm.man:
+
+2006-09-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm_wire.man: Clarified the use of the TCP port in the initial
+ message a bit more, i.e. the meaning of the special value '0'.
+
+ * comm.tcl: The rewrite of the hook handling broken the promised
+ * comm.man: semantics. Fixed. Also extended the handling of a
+ configured -interp to deal with a variety of possibilities
+ regarding missing or hidden commands. Updated the documentation.
+
+ * pkgIndex.tcl: Bumped to version 4.3.2
+
+2006-08-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.man: Bumped to version 4.3.1
+ * comm.tcl:
+ * pkgIndex.tcl:
+
+ * comm.tcl: Implemented the new options -interp and -events,
+ * comm.test: extended the testsuite to cover them. Created
+ utility/helper command for the execution of hook scripts, and
+ rewrote all hook places to use it.
+
+2006-08-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.man: Documented an easier use of slave interpreters
+ (-interp, -events).
+
+2006-08-11 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.test: Moved startup and cleanup of slave process
+ * comm.slaveboot: into a separate file.
+
+2006-01-28 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.test: Fixed cleanup of temp. files.
+
+2006-01-22 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.test: More boilerplate simplified via use of test support.
+
+2006-01-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.test: Hooked into the new common test support code.
+
+2006-01-10 Andreas Kupries <andreask@activestate.com>
+
+ * comm.test: Fixed [SF Tcllib Bug 1316033]. Uncluttering test
+ output.
+
+2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.8 ========================
+ *
+
+2005-10-03 Andreas Kupries <andreask@activestate.com>
+
+ * comm.tcl: Accepted [SF Tcllib Bug 1006282], which is actually an
+ * comm.man: RFE. Comm channels are extended with an option which
+ allows the user to force the server side to silently
+ ignore connection attempts where the protocol
+ negotiation with the other side failed.
+
+2005-03-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm_wire.man: Added documentation for the wire protocol run by
+ comm internally.
+
+2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.7 ========================
+ *
+
+2004-08-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.tcl: Typo police.
+ * comm.man:
+
+2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6.1 ========================
+ *
+
+2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6 ========================
+ *
+
+2003-10-23 Andreas Kupries <andreask@activestate.com>
+
+ * comm.man: Updated version number in documentation.
+
+2003-10-21 Jeff Hobbs <jeffh@ActiveState.com>
+
+ * pkgIndex.tcl: updated to v4.2.
+
+ * comm.man:
+ * comm.tcl (comm_cmd_new): make 'comm::comm new ?chan?' fully
+ qualify the namespace of the new channel and return that.
+ [Bug #741653, #817351]
+
+ * comm.tcl: change default encoding to utf-8. This should still
+ work with other versions of comm because the previous one-sided
+ binary setting only allowed for limited i18n-ness. Using this
+ version of comm on both sides will ensure full i18n-happiness.
+ [Bug #806420]
+
+2003-05-09 Jeff Hobbs <jeffh@ActiveState.com>
+
+ * pkgIndex.tcl: updated to comm 4.1
+ * comm.man:
+ * comm.tcl: rewrite of code to remove pseudo-object model.
+ Clean up code, add send -command callback to allow for
+ notification of results for asynchronous sends.
+
+2003-05-08 Jeff Hobbs <jeffh@ActiveState.com>
+
+ * comm.tcl: update use of string functions to 8.2 cleanliness.
+
+2003-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.4 ========================
+ *
+
+2003-04-11 Andreas Kupries <andreask@activestate.com>
+
+ * comm.man:
+ * comm.tcl:
+ * pkgIndex.tcl: Set version of the package to to 4.0.1.
+
+2003-01-28 David N. Welton <davidw@dedasys.com>
+
+ * comm.tcl (::comm::commConfigure): Use 'string is integer'
+ instead of regexp's.
+ Require Tcl 8.2.
+
+2003-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.man: More semantic markup, less visual one.
+
+2002-08-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.test: Removed writing of file ~/foo, was debugging
+ code. Changed creation and usage of file 'spawn' to allow an
+ arbitrary setting of -tmpdir. Fixes SF Bug #589225 reported by
+ Don Porter <dgp@users.sourceforge.net>.
+
+2002-03-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * Bumped version number to 4.0 per request by John LoVerso.
+
+ * comm.tcl: Applied patch #526499 improving the handling of errors
+ for async invoked commands.
+
+2002-02-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.tcl: Frink run.
+
+2002-01-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * Bumped version to 3.7.1.
+
+2001-11-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * comm.n: Updated to reflect the changes in the comm code
+ (namespaces). This fixes SF item #480227.
+
+ * comm.tcl: Fixed two places where namespacing was not handled
+ correctly.
+
+2001-08-22 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * Integrated into tcllib.
diff --git a/tcllib/modules/comm/comm.LICENSE b/tcllib/modules/comm/comm.LICENSE
new file mode 100644
index 0000000..3e87505
--- /dev/null
+++ b/tcllib/modules/comm/comm.LICENSE
@@ -0,0 +1,48 @@
+Copyright (C) 1995-1998, The Open Group. All Rights Reserved.
+
+This software was developed by the Open Group Research Institute
+("RI"). This software, both binary and source (hereafter, Software)
+is copyrighted by The Open Group Research Institute and ownership
+remains with the RI.
+
+The RI hereby grants you (hereafter, Licensee) permission to use,
+copy, modify, distribute, and license this Software and its
+documentation for any purpose, provided that existing copyright
+notices are retained in all copies and that this notice is included
+verbatim in any distributions. No written agreement, license, or
+royalty fee is required for any of the authorized uses provided
+that the RI is publicly and prominently acknowledged as the source
+of this software.
+
+Licensee may make derivative works. However, if Licensee distributes
+any derivative work based on or derived from the Software, then
+Licensee will (1) notify the RI regarding its distribution of the
+derivative work, (2) clearly notify users that such derivative work
+is a modified version and not the original software distributed by
+the RI, and (3) the RI is publicly and prominently acknowledged as
+the source of this software.
+
+THE RI MAKES NO REPRESENTATIONS ABOUT THE SERVICEABILITY OF THIS
+SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS
+OR IMPLIED WARRANTY. THE RI SHALL NOT BE LIABLE FOR ANY DAMAGES
+SUFFERED BY THE USERS OF THIS SOFTWARE.
+
+By using or copying this Software, Licensee agrees to abide by the
+copyright law and all other applicable laws of the U.S. including,
+but not limited to, export control laws, and the terms of this
+license. The RI shall have the right to terminate this license
+immediately by written notice upon Licensee's breach of, or
+non-compliance with, any of its terms. Licensee may be held legally
+responsible for any copyright infringement that is caused or
+encouraged by Licensee's failure to abide by the terms of this
+license.
+
+Comments and questions on this license are welcome and can be sent to:
+
+ ri-software@opengroup.org
+
+Comments and questions on this software should be sent to the author:
+
+ j.loverso@opengroup.org
+ john@loverso.southborough.ma.us
+
diff --git a/tcllib/modules/comm/comm.man b/tcllib/modules/comm/comm.man
new file mode 100644
index 0000000..be1290a
--- /dev/null
+++ b/tcllib/modules/comm/comm.man
@@ -0,0 +1,1230 @@
+[vset COMM_VERSION 4.6.3]
+[manpage_begin comm n [vset COMM_VERSION]]
+[see_also send(n)]
+[keywords comm]
+[keywords communication]
+[keywords ipc]
+[keywords message]
+[keywords {remote communication}]
+[keywords {remote execution}]
+[keywords rpc]
+[keywords secure]
+[keywords send]
+[keywords socket]
+[keywords ssl]
+[keywords tls]
+[copyright {1995-1998 The Open Group. All Rights Reserved.}]
+[copyright {2003-2004 ActiveState Corporation.}]
+[copyright {2006-2009 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Remote communication}]
+[titledesc {A remote communication facility for Tcl (8.3 and later)}]
+[category {Programming tools}]
+[require Tcl 8.3]
+[require comm [opt [vset COMM_VERSION]]]
+[description]
+
+[para]
+
+The [package comm] command provides an inter-interpreter remote
+execution facility much like Tk's [cmd send(n)], except that it uses
+sockets rather than the X server for the communication path. As a
+result, [package comm] works with multiple interpreters, works on
+Windows and Macintosh systems, and provides control over the remote
+execution path.
+
+[para]
+
+These commands work just like [cmd send] and [cmd {winfo interps}] :
+
+[para]
+[example {
+ ::comm::comm send ?-async? id cmd ?arg arg ...?
+ ::comm::comm interps
+}]
+[para]
+
+This is all that is really needed to know in order to use
+[package comm]
+
+[subsection Commands]
+
+The package initializes [cmd ::comm::comm] as the default [emph chan].
+
+[para]
+[package comm] names communication endpoints with an [emph id] unique
+to each machine. Before sending commands, the [emph id] of another
+interpreter is needed. Unlike Tk's send, [package comm] doesn't
+implicitly know the [emph id]'s of all the interpreters on the system.
+
+The following four methods make up the basic [package comm] interface.
+
+[list_begin definitions]
+
+[call [cmd {::comm::comm send}] [opt -async] \
+ [opt "-command [arg callback]"] \
+ [arg id] [arg cmd] [opt [arg {arg arg ...}]]]
+
+This invokes the given command in the interpreter named by [arg id]. The
+command waits for the result and remote errors are returned unless the
+[option -async] or [option -command] option is given. If [option -async]
+is given, send returns immediately and there is no further notification of
+result. If [option -command] is used, [emph callback] specifies a command
+to invoke when the result is received. These options are mutually
+exclusive. The callback will receive arguments in the form
+[emph {-option value}], suitable for [cmd {array set}].
+
+The options are: [emph -id], the comm id of the interpreter that received
+the command; [emph -serial], a unique serial for each command sent to a
+particular comm interpreter; [emph -chan], the comm channel name;
+[emph -code], the result code of the command; [emph -errorcode], the
+errorcode, if any, of the command; [emph -errorinfo], the errorinfo, if
+any, of the command; and [emph -result], the return value of the command.
+If connection is lost before a reply is received, the callback will be
+invoked with a connection lost message with -code equal to -1. When
+[option -command] is used, the command returns the unique serial for the
+command.
+
+[call [cmd {::comm::comm self}]]
+
+Returns the [emph id] for this channel.
+
+[call [cmd {::comm::comm interps}]]
+
+Returns a list of all the remote [emph id]'s to which this channel is
+connected. [package comm] learns a new remote [emph id] when a command
+is first issued it, or when a remote [emph id] first issues a command
+to this comm channel. [cmd {::comm::comm ids}] is an alias for this
+method.
+
+[call [cmd {::comm::comm connect}] [opt [arg id]]]
+
+Whereas [cmd {::comm::comm send}] will automatically connect to the
+given [arg id], this forces a connection to a remote [emph id] without
+sending a command. After this, the remote [emph id] will appear in
+[cmd {::comm::comm interps}].
+
+[list_end]
+
+[subsection {Eval Semantics}]
+[para]
+
+The evaluation semantics of [cmd {::comm::comm send}] are intended to
+match Tk's [cmd send] [emph exactly]. This means that [package comm]
+evaluates arguments on the remote side.
+
+[para]
+
+If you find that [cmd {::comm::comm send}] doesn't work for a
+particular command, try the same thing with Tk's send and see if the
+result is different. If there is a problem, please report it. For
+instance, there was had one report that this command produced an
+error. Note that the equivalent [cmd send] command also produces the
+same error.
+
+[para]
+[example {
+ % ::comm::comm send id llength {a b c}
+ wrong # args: should be "llength list"
+ % send name llength {a b c}
+ wrong # args: should be "llength list"
+}]
+[para]
+
+The [cmd eval] hook (described below) can be used to change from
+[cmd send]'s double eval semantics to single eval semantics.
+
+[subsection {Multiple Channels}]
+[para]
+
+More than one [cmd comm] channel (or [emph listener]) can be created
+in each Tcl interpreter. This allows flexibility to create full and
+restricted channels. For instance, [term hook] scripts are specific
+to the channel they are defined against.
+
+[list_begin definitions]
+
+[call [cmd {::comm::comm new}] [arg chan] [opt [arg {name value ...}]]]
+
+This creates a new channel and Tcl command with the given channel
+name. This new command controls the new channel and takes all the
+same arguments as [cmd ::comm::comm]. Any remaining arguments are
+passed to the [cmd config] method. The fully qualified channel
+name is returned.
+
+[call [cmd {::comm::comm channels}]]
+
+This lists all the channels allocated in this Tcl interpreter.
+
+[list_end]
+[para]
+
+The default configuration parameters for a new channel are:
+
+[para]
+[example {
+ "-port 0 -local 1 -listen 0 -silent 0"
+}]
+[para]
+
+The default channel [cmd ::comm::comm] is created with:
+
+[para]
+[example {
+ "::comm::comm new ::comm::comm -port 0 -local 1 -listen 1 -silent 0"
+}]
+
+[subsection {Channel Configuration}]
+[para]
+
+The [cmd config] method acts similar to [cmd fconfigure] in that it
+sets or queries configuration variables associated with a channel.
+
+[list_begin definitions]
+[call [cmd {::comm::comm config}]]
+[call [cmd {::comm::comm config}] [arg name]]
+[call [cmd {::comm::comm config}] [opt "[arg name] [arg value] [arg ...]"]]
+
+When given no arguments, [cmd config] returns a list of all variables
+and their value With one argument, [cmd config] returns the value of
+just that argument. With an even number of arguments, the given
+variables are set to the given values.
+
+[list_end]
+
+[para]
+
+These configuration variables can be changed (descriptions of them are
+elsewhere in this manual page):
+
+[list_begin definitions]
+[def "[option -listen] [opt [arg 0|1]]"]
+[def "[option -local] [opt [arg 0|1]]"]
+[def "[option -port] [opt [arg port]]"]
+[def "[option -silent] [opt [arg 0|1]]"]
+[def "[option -socketcmd] [opt [arg commandname]]"]
+
+[def "[option -interp] [opt [arg interpreter]]"]
+[def "[option -events] [opt [arg eventlist]]"]
+[list_end]
+
+[para]
+These configuration variables are read only:
+
+[list_begin definitions]
+[def "[option -chan] [arg chan]"]
+[def "[option -serial] [arg n]"]
+[def "[option -socket] sock[arg In]"]
+[list_end]
+
+[para]
+
+When [cmd config] changes the parameters of an existing channel (with
+the exception of [option -interp] and [option -events]), it closes and
+reopens the listening socket.
+
+An automatically assigned channel [emph id] will change when this
+happens.
+
+Recycling the socket is done by invoking [cmd {::comm::comm abort}],
+which causes all active sends to terminate.
+
+[subsection {Id/port Assignments}]
+[para]
+
+[package comm] uses a TCP port for endpoint [emph id]. The
+
+[method interps] (or [method ids]) method merely lists all the TCP ports
+to which the channel is connected. By default, each channel's
+
+[emph id] is randomly assigned by the operating system (but usually
+starts at a low value around 1024 and increases each time a new socket
+is opened). This behavior is accomplished by giving the
+
+[option -port] config option a value of 0. Alternately, a specific
+TCP port number may be provided for a given channel. As a special
+case, comm contains code to allocate a a high-numbered TCP port
+(>10000) by using [option {-port {}}]. Note that a channel won't be
+created and initialized unless the specific port can be allocated.
+
+[para]
+
+As a special case, if the channel is configured with
+
+[option {-listen 0}], then it will not create a listening socket and
+will use an id of [const 0] for itself. Such a channel is only good
+for outgoing connections (although once a connection is established,
+it can carry send traffic in both directions).
+
+As another special case, if the channel is configured with
+
+[option {-silent 0}], then the listening side will ignore connection
+attempts where the protocol negotiation phase failed, instead of
+throwing an error.
+
+[subsection {Execution Environment}]
+
+A communication channel in its default configuration will use the
+current interpreter for the execution of all received scripts, and of
+the event scripts associated with the various hooks.
+
+[para]
+
+This insecure setup can be changed by the user via the two options
+[option -interp], and [option -events].
+
+[para]
+
+When [option -interp] is set all received scripts are executed in the
+slave interpreter specified as the value of the option. This
+interpreter is expected to exist before configuration. I.e. it is the
+responsibility of the user to create it. However afterward the
+communication channel takes ownership of this interpreter, and will
+destroy it when the communication channel is destroyed.
+
+Note that reconfiguration of the communication channel to either a
+different interpreter or the empty string will release the ownership
+[emph without] destroying the previously configured interpreter. The
+empty string has a special meaning, it restores the default behaviour
+of executing received scripts in the current interpreter.
+
+[para]
+
+[emph {Also of note}] is that replies and callbacks (a special form of
+reply) are [emph not] considered as received scripts. They are
+trusted, part of the internal machinery of comm, and therefore always
+executed in the current interpreter.
+
+[para]
+
+Even if an interpreter has been configured as the execution
+environment for received scripts the event scripts associated with the
+various hooks will by default still be executed in the current
+interpreter. To change this use the option [option -events] to declare
+a list of the events whose scripts should be executed in the declared
+interpreter as well. The contents of this option are ignored if the
+communication channel is configured to execute received scripts in the
+current interpreter.
+
+[subsection {Remote Interpreters}]
+[para]
+
+By default, each channel is restricted to accepting connections from
+the local system. This can be overridden by using the
+
+[option {-local 0}] configuration option For such channels, the
+
+[emph id] parameter takes the form [emph "\{ id host \}"].
+
+[para]
+
+[emph WARNING]: The [emph host] must always be specified in the same
+form (e.g., as either a fully qualified domain name, plain hostname or
+an IP address).
+
+[subsection {Closing Connections}]
+[para]
+
+These methods give control over closing connections:
+
+[list_begin definitions]
+
+[call [cmd {::comm::comm shutdown}] [arg id]]
+
+This closes the connection to [arg id], aborting all outstanding
+commands in progress. Note that nothing prevents the connection from
+being immediately reopened by another incoming or outgoing command.
+
+[call [cmd {::comm::comm abort}]]
+
+This invokes shutdown on all open connections in this comm channel.
+
+[call [cmd {::comm::comm destroy}]]
+
+This aborts all connections and then destroys the this comm channel
+itself, including closing the listening socket. Special code allows
+the default [cmd ::comm::comm] channel to be closed such that the
+
+[cmd ::comm::comm] command it is not destroyed. Doing so closes the
+listening socket, preventing both incoming and outgoing commands on
+the channel. This sequence reinitializes the default channel:
+
+[para]
+[example {
+ "::comm::comm destroy; ::comm::comm new ::comm::comm"
+}]
+
+[list_end]
+
+[para]
+
+When a remote connection is lost (because the remote exited or called
+[cmd shutdown]), [package comm] can invoke an application callback.
+This can be used to cleanup or restart an ancillary process, for
+instance. See the [term lost] callback below.
+
+[subsection Callbacks]
+[para]
+This is a mechanism for setting hooks for particular events:
+
+[list_begin definitions]
+
+[call [cmd {::comm::comm hook}] [arg event] [opt [const +]] [opt [arg script]]]
+
+This uses a syntax similar to Tk's [cmd bind] command. Prefixing
+
+[arg script] with a [const +] causes the new script to be appended.
+Without this, a new [arg script] replaces any existing script. When
+invoked without a script, no change is made. In all cases, the new
+hook script is returned by the command.
+
+[para]
+
+When an [arg event] occurs, the [arg script] associated with it is
+evaluated with the listed variables in scope and available. The
+return code ([emph not] the return value) of the script is commonly
+used decide how to further process after the hook.
+
+[para]
+Common variables include:
+
+[list_begin definitions]
+
+[def [var chan]]
+the name of the comm channel (and command)
+
+[def [var id]]
+the id of the remote in question
+
+[def [var fid]]
+the file id for the socket of the connection
+
+[list_end]
+[list_end]
+
+[para]
+These are the defined [emph events]:
+
+[list_begin definitions]
+
+[def [const connecting]]
+
+Variables:
+[var chan], [var id]
+[comment {[var host], and [var port] are NOT defined when this is called}]
+[para]
+
+This hook is invoked before making a connection to the remote named in
+[arg id]. An error return (via [cmd error]) will abort the connection
+attempt with the error. Example:
+
+[para]
+[example {
+ % ::comm::comm hook connecting {
+ if {[string match {*[02468]} $id]} {
+ error "Can't connect to even ids"
+ }
+ }
+ % ::comm::comm send 10000 puts ok
+ Connect to remote failed: Can't connect to even ids
+ %
+}]
+
+[def [const connected]]
+
+Variables:
+[var chan], [var fid], [var id], [var host], and [var port].
+[para]
+
+This hook is invoked immediately after making a remote connection to
+[arg id], allowing arbitrary authentication over the socket named by
+[arg fid]. An error return (via [cmd error] ) will close the
+connection with the error. [arg host] and [arg port] are merely
+extracted from the [arg id]; changing any of these will have no effect
+on the connection, however. It is also possible to substitute and
+replace [arg fid].
+
+[def [const incoming]]
+
+Variables:
+[var chan], [var fid], [var addr], and [var remport].
+[para]
+
+Hook invoked when receiving an incoming connection, allowing arbitrary
+authentication over socket named by [arg fid]. An error return (via
+[cmd error]) will close the connection with the error. Note that the
+peer is named by [arg remport] and [arg addr] but that the remote
+[emph id] is still unknown. Example:
+
+[para]
+[example {
+ ::comm::comm hook incoming {
+ if {[string match 127.0.0.1 $addr]} {
+ error "I don't talk to myself"
+ }
+ }
+}]
+
+[def [const eval]]
+
+Variables:
+[var chan], [var id], [var cmd], and [var buffer].
+[para]
+
+This hook is invoked after collecting a complete script from a remote
+but [emph before] evaluating it. This allows complete control over
+the processing of incoming commands. [arg cmd] contains either
+[const send] or [const async]. [arg buffer] holds the script to
+evaluate. At the time the hook is called, [arg {$chan remoteid}] is
+identical in value to [arg id].
+
+[para]
+
+By changing [arg buffer], the hook can change the script to be
+evaluated. The hook can short circuit evaluation and cause a value to
+be immediately returned by using [cmd return] [arg result] (or, from
+within a procedure, [cmd {return -code return}] [arg result]). An
+error return (via [cmd error]) will return an error result, as is if
+the script caused the error. Any other return will evaluate the
+script in [arg buffer] as normal. For compatibility with 3.2,
+
+[cmd break] and [cmd {return -code break}] [arg result] is supported,
+acting similarly to [cmd {return {}}] and [cmd {return -code return}]
+[arg result].
+
+[para]
+
+Examples:
+
+[list_begin enumerated]
+
+[enum]
+augmenting a command
+[para]
+[example {
+ % ::comm::comm send [::comm::comm self] pid
+ 5013
+ % ::comm::comm hook eval {puts "going to execute $buffer"}
+ % ::comm::comm send [::comm::comm self] pid
+ going to execute pid
+ 5013
+}]
+
+[enum]
+short circuiting a command
+[para]
+[example {
+ % ::comm::comm hook eval {puts "would have executed $buffer"; return 0}
+ % ::comm::comm send [::comm::comm self] pid
+ would have executed pid
+ 0
+}]
+
+[enum]
+Replacing double eval semantics
+[para]
+[example {
+ % ::comm::comm send [::comm::comm self] llength {a b c}
+ wrong # args: should be "llength list"
+ % ::comm::comm hook eval {return [uplevel #0 $buffer]}
+ return [uplevel #0 $buffer]
+ % ::comm::comm send [::comm::comm self] llength {a b c}
+ 3
+}]
+
+[enum]
+Using a slave interpreter
+[para]
+[example {
+ % interp create foo
+ % ::comm::comm hook eval {return [foo eval $buffer]}
+ % ::comm::comm send [::comm::comm self] set myvar 123
+ 123
+ % set myvar
+ can't read "myvar": no such variable
+ % foo eval set myvar
+ 123
+}]
+
+[enum]
+Using a slave interpreter (double eval)
+[para]
+[example {
+ % ::comm::comm hook eval {return [eval foo eval $buffer]}
+}]
+
+[enum]
+Subverting the script to execute
+[para]
+[example {
+ % ::comm::comm hook eval {
+ switch -- $buffer {
+ a {return A-OK}
+ b {return B-OK}
+ default {error "$buffer is a no-no"}
+ }
+ }
+ % ::comm::comm send [::comm::comm self] pid
+ pid is a no-no
+ % ::comm::comm send [::comm::comm self] a
+ A-OK
+}]
+
+[list_end]
+
+[def [const reply]]
+
+Variables:
+[var chan], [var id], [var buffer], [var ret], and [var return()].
+[para]
+
+This hook is invoked after collecting a complete reply script from a
+remote but [emph before] evaluating it. This allows complete
+control over the processing of replies to sent commands. The reply
+[arg buffer] is in one of the following forms
+
+[list_begin itemized]
+[item]
+return result
+[item]
+return -code code result
+[item]
+return -code code -errorinfo info -errorcode ecode msg
+[list_end]
+[para]
+
+For safety reasons, this is decomposed. The return result is in
+[arg ret], and the return switches are in the return array:
+
+[list_begin itemized]
+[item]
+[emph return(-code)]
+[item]
+[emph return(-errorinfo)]
+[item]
+[emph return(-errorcode)]
+[list_end]
+[para]
+
+Any of these may be the empty string. Modifying these four variables
+can change the return value, whereas modifying [arg buffer] has no
+effect.
+
+[def [const callback]]
+
+Variables:
+[var chan], [var id], [var buffer], [var ret], and [var return()].
+[para]
+
+Similar to [emph reply], but used for callbacks.
+
+[def [const lost]]
+
+Variables:
+[var chan], [var id], and [var reason].
+[para]
+
+This hook is invoked when the connection to [var id] is lost. Return
+value (or thrown error) is ignored. [arg reason] is an explanatory
+string indicating why the connection was lost. Example:
+
+[para]
+
+[example {
+ ::comm::comm hook lost {
+ global myvar
+ if {$myvar(id) == $id} {
+ myfunc
+ return
+ }
+ }
+}]
+
+[list_end]
+
+[subsection Unsupported]
+[para]
+These interfaces may change or go away in subsequence releases.
+
+[list_begin definitions]
+[call [cmd {::comm::comm remoteid}]]
+
+Returns the [arg id] of the sender of the last remote command
+executed on this channel. If used by a proc being invoked remotely,
+it must be called before any events are processed. Otherwise, another
+command may get invoked and change the value.
+
+[call [cmd ::comm::comm_send]]
+
+Invoking this procedure will substitute the Tk [cmd send] and
+[cmd {winfo interps}] commands with these equivalents that use
+[cmd ::comm::comm].
+
+[para]
+
+[example {
+ proc send {args} {
+ eval ::comm::comm send $args
+ }
+ rename winfo tk_winfo
+ proc winfo {cmd args} {
+ if {![string match in* $cmd]} {
+ return [eval [list tk_winfo $cmd] $args]
+ }
+ return [::comm::comm interps]
+ }
+}]
+
+[list_end]
+
+[subsection Security]
+
+Starting with version 4.6 of the package an option [option -socketcmd]
+is supported, allowing the user of a comm channel to specify which
+command to use when opening a socket. Anything which is API-compatible
+with the builtin [cmd ::socket] (the default) can be used.
+
+[para]
+
+The envisioned main use is the specification of the [cmd tls::socket]
+command, see package [package tls], to secure the communication.
+
+[para]
+[example {
+ # Load and initialize tls
+ package require tls
+ tls::init -cafile /path/to/ca/cert -keyfile ...
+
+ # Create secured comm channel
+ ::comm::comm new SECURE -socketcmd tls::socket -listen 1
+ ...
+}]
+
+[para]
+
+The sections [sectref {Execution Environment}] and [sectref Callbacks]
+are also relevant to the security of the system, providing means to
+restrict the execution to a specific environment, perform additional
+authentication, and the like.
+
+[subsection {Blocking Semantics}]
+
+[para]
+
+There is one outstanding difference between [package comm] and
+
+[cmd send]. When blocking in a synchronous remote command, [cmd send]
+uses an internal C hook (Tk_RestrictEvents) to the event loop to look
+ahead for send-related events and only process those without
+processing any other events. In contrast, [package comm] uses the
+
+[cmd vwait] command as a semaphore to indicate the return message has
+arrived. The difference is that a synchronous [cmd send] will block
+the application and prevent all events (including window related ones)
+from being processed, while a synchronous [cmd {::comm::comm send}]
+will block the application but still allow other events to get
+processed. In particular, [cmd {after idle}] handlers will fire
+immediately when comm blocks.
+
+[para]
+
+What can be done about this? First, note that this behavior will come
+from any code using [cmd vwait] to block and wait for an event to
+occur. At the cost of multiple channel support, [package comm] could
+be changed to do blocking I/O on the socket, giving send-like blocking
+semantics. However, multiple channel support is a very useful feature
+of comm that it is deemed too important to lose. The remaining
+approaches involve a new loadable module written in C (which is
+somewhat against the philosophy of [cmd comm ]) One way would be to
+create a modified version of the [cmd vwait] command that allow the
+event flags passed to Tcl_DoOneEvent to be specified. For [cmd comm],
+just the TCL_FILE_EVENTS would be processed. Another way would be to
+implement a mechanism like Tk_RestrictEvents, but apply it to the Tcl
+event loop (since [package comm] doesn't require Tk). One of these
+approaches will be available in a future [package comm] release as an
+optional component.
+
+[subsection {Asynchronous Result Generation}]
+
+By default the result returned by a remotely invoked command is the
+result sent back to the invoker. This means that the result is
+generated synchronously, and the server handling the call is blocked
+for the duration of the command.
+
+[para]
+
+While this is tolerable as long as only short-running commands are
+invoked on the server long-running commands, like database queries
+make this a problem. One command can prevent the processing requests
+of all other clients for an arbitrary period of time.
+
+[para]
+
+Before version 4.5 of comm the only solution was to rewrite the server
+command to use the Tcl builtin command [cmd vwait], or one of its
+relatives like [cmd tkwait], to open a new event loop which processes
+requests while the long-running operation is executed. This however
+has its own perils, as this makes it possible to both overflow the Tcl
+stack with a large number of event loop, and to have a newer requests
+block the return of older ones, as the eventloop have to be unwound in
+the order of their creation.
+
+[para]
+
+The proper solution is to have the invoked command indicate to
+[package comm] that it cannot or will not deliver an immediate,
+synchronous result, but will do so later. At that point the framework
+can put sending the actual result on hold and continue processing
+requests using the main event loop. No blocking, no nesting of event
+loops. At some future date the long running operation delivers the
+result to comm, via the future object, which is then forwarded to the
+invoker as usual.
+
+[para]
+
+The necessary support for this solution has been added to comm since
+version 4.5, in the form of the new method [method return_async].
+
+[list_begin definitions]
+[call [cmd {::comm::comm return_async}]]
+
+This command is used by a remotely invoked script to notify the comm
+channel which invoked it that the result to send back to the invoker
+is not generated synchronously. If this command is not called the
+default/standard behaviour of comm is to send the synchronously
+generated result of the script itself to the invoker.
+
+[para]
+
+The result of [cmd return_async] is an object. This object, called a
+[term future] is where the result of the script has to be delivered to
+when it becomes ready. When that happens it will take all the
+necessary actions to deliver the result to the invoker of the script,
+and then destroy itself. Should comm have lost the connection to the
+invoker while the result is being computed the future will not try to
+deliver the result it got, but just destroy itself. The future can be
+configured with a command to call when the invoker is lost. This
+enables the user to implement an early abort of the long-running
+operation, should this be supported by it.
+
+[para]
+An example:
+
+[example {
+# Procedure invoked by remote clients to run database operations.
+proc select {sql} {
+ # Signal the async generation of the result
+
+ set future [::comm::comm return_async]
+
+ # Generate an async db operation and tell it where to deliver the result.
+
+ set query [db query -command [list $future return] $sql]
+
+ # Tell the database system which query to cancel if the connection
+ # goes away while it is running.
+
+ $future configure -command [list db cancel $query]
+
+ # Note: The above will work without problem only if the async
+ # query will nover run its completion callback immediately, but
+ # only from the eventloop. Because otherwise the future we wish to
+ # configure may already be gone. If that is possible use 'catch'
+ # to prevent the error from propagating.
+ return
+}
+}]
+[para]
+
+The API of a future object is:
+
+[list_begin definitions]
+[call [cmd \$future] [method return] [opt "[option -code] [arg code]"] [opt [arg value]]]
+
+Use this method to tell the future that long-running operation has
+completed. Arguments are an optional return value (defaults to the
+empty string), and the Tcl return code (defaults to OK).
+
+[para]
+
+The future will deliver this information to invoker, if the connection
+was not lost in the meantime, and then destroy itself. If the
+connection was lost it will do nothing but destroy itself.
+
+[call [cmd \$future] [method configure] [opt "[option -command] [opt [arg cmdprefix]]"]]
+[call [cmd \$future] [method cget] [option -command]]
+
+These methods allow the user to retrieve and set a command to be
+called if the connection the future belongs to has been lost.
+
+[list_end]
+
+[list_end]
+
+[subsection Compatibility]
+[para]
+
+[package comm] exports itself as a package. The package version number
+is in the form [emph "major . minor"], where the major version will
+only change when a non-compatible change happens to the API or
+protocol. Minor bug fixes and changes will only affect the minor
+version. To load [package comm] this command is usually used:
+
+[para]
+[example {
+ package require comm 3
+}]
+
+[para]
+Note that requiring no version (or a specific version) can also be done.
+
+[para]
+The revision history of [package comm] includes these releases:
+
+[list_begin definitions]
+
+[def 4.6.3]
+
+Fixed ticket [lb]ced0d60fc9[rb]. Added proper detection of eof on a
+socket, properly closing it.
+
+[def 4.6.2]
+
+Fixed bugs 2972571 and 3066872, the first a misdetection of quoted
+brace after double backslash, the other a blocking gets making for an
+obvious (hinsight) DoS attack on comm channels.
+
+[def 4.6.1]
+
+Changed the implementation of [cmd comm::commCollect] to emulate
+lindex's pre-Tcl 8 behaviour, i.e. it was given the ability to parse
+out the first word of a list, even if the whole buffer is not a
+well-formed list. Without this change the first word could only be
+extracted if the whole buffer was a well-formed list (ever since Tcl
+8), and in a ver-high-load situation, i.e. a server sending lots
+and/or large commands very fast, this may never happen, eventually
+crashing the receiver when it runs out of memory. With the change the
+receiver is always able to process the first word when it becomes
+well-formed, regardless of the structure of the remainder of the
+buffer.
+
+[def 4.6]
+
+Added the option [option -socketcmd] enabling users to override how a
+socket is opened. The envisioned main use is the specification of the
+[cmd tls::socket] command, see package [package tls], to secure the
+communication.
+
+[def 4.5.7]
+
+Changed handling of ports already in use to provide a proper error
+message.
+
+[def 4.5.6]
+
+Bugfix in the replacement for [cmd vwait], made robust against of
+variable names containing spaces.
+
+[def 4.5.5]
+
+Bugfix in the handling of hooks, typo in variable name.
+
+[def 4.5.4]
+
+Bugfix in the handling of the result received by the [method send]
+method. Replaced an [emph {after idle unset result}] with an immediate
+[cmd unset], with the information saved to a local variable.
+
+[para]
+
+The [cmd {after idle}] can spill into a forked child process if there
+is no event loop between its setup and the fork. This may bork the
+child if the next event loop is the [cmd vwait] of [package comm]'s
+[method send] a few lines above the [cmd {after idle}], and the child
+used the same serial number for its next request. In that case the
+parent's [cmd {after idle unset}] will delete the very array element
+the child is waiting for, unlocking the [cmd vwait], causing it to
+access a now missing array element, instead of the expected result.
+
+[def 4.5.3]
+
+Bugfixes in the wrappers for the builtin [cmd update] and [cmd vwait]
+commands.
+
+[def 4.5.2]
+
+Bugfix in the wrapper for the builtin [cmd update] command.
+
+[def 4.5.1]
+
+Bugfixes in the handling of -interp for regular scripts. The handling
+of the buffer was wrong for scripts which are a single statement as
+list. Fixed missing argument to new command [cmd commSendReply],
+introduced by version 4.5. Affected debugging.
+
+[def 4.5]
+
+New server-side feature. The command invoked on the server can now
+switch comm from the standard synchronous return of its result to an
+asynchronous (defered) return. Due to the use of snit to implement the
+[term future] objects used by this feature from this version on comm
+requires at least Tcl 8.3 to run. Please read the section
+[sectref {Asynchronous Result Generation}] for more details.
+
+[def 4.4.1]
+
+Bugfix in the execution of hooks.
+
+[def 4.4]
+
+Bugfixes in the handling of -interp for regular and hook
+scripts. Bugfixes in channel cleanup.
+
+[def 4.3.1]
+
+Introduced -interp and -events to enable easy use of a slave interp
+for execution of received scripts, and of event scripts.
+
+[def 4.3]
+
+Bugfixes, and introduces -silent to allow the user to force the
+server/listening side to silently ignore connection attempts where the
+protocol negotiation failed.
+
+[def 4.2]
+
+Bugfixes, and most important, switched to utf-8 as default encoding
+for full i18n without any problems.
+
+[def 4.1]
+
+Rewrite of internal code to remove old pseudo-object model. Addition
+of send -command asynchronous callback option.
+
+[def 4.0]
+
+Per request by John LoVerso. Improved handling of error for async
+invoked commands.
+
+[def 3.7]
+
+Moved into tcllib and placed in a proper namespace.
+
+[def 3.6]
+
+A bug in the looking up of the remoteid for a executed command could
+be triggered when the connection was closed while several asynchronous
+sends were queued to be executed.
+
+[def 3.5]
+
+Internal change to how reply messages from a [cmd send] are handled.
+Reply messages are now decoded into the [arg value] to pass to
+
+[cmd return]; a new return statement is then cons'd up to with this
+value. Previously, the return code was passed in from the remote as a
+command to evaluate. Since the wire protocol has not changed, this is
+still the case. Instead, the reply handling code decodes the
+
+[const reply] message.
+
+[def 3.4]
+
+Added more source commentary, as well as documenting config variables
+in this man page. Fixed bug were loss of connection would give error
+about a variable named [var pending] rather than the message about
+the lost connection. [cmd {comm ids}] is now an alias for
+
+[cmd {comm interps}] (previously, it an alias for [cmd {comm chans}]).
+Since the method invocation change of 3.0, break and other exceptional
+conditions were not being returned correctly from [cmd {comm send}].
+This has been fixed by removing the extra level of indirection into
+the internal procedure [cmd commSend]. Also added propagation of
+the [arg errorCode] variable. This means that these commands return
+exactly as they would with [cmd send]:
+
+[para]
+[example {
+ comm send id break
+ catch {comm send id break}
+ comm send id expr 1 / 0
+}]
+[para]
+
+Added a new hook for reply messages. Reworked method invocation to
+avoid the use of comm:* procedures; this also cut the invocation time
+down by 40%. Documented [cmd {comm config}] (as this manual page
+still listed the defunct [cmd {comm init}]!)
+
+[def 3.3]
+
+Some minor bugs were corrected and the documentation was cleaned up.
+Added some examples for hooks. The return semantics of the [cmd eval]
+hook were changed.
+
+[def 3.2]
+
+A new wire protocol, version 3, was added. This is backwards
+compatible with version 2 but adds an exchange of supported protocol
+versions to allow protocol negotiation in the future. Several bugs
+with the hook implementation were fixed. A new section of the man
+page on blocking semantics was added.
+
+[def 3.1]
+
+All the documented hooks were implemented. [cmd commLostHook] was
+removed. A bug in [cmd {comm new}] was fixed.
+
+[def 3.0]
+
+This is a new version of [package comm] with several major changes.
+There is a new way of creating the methods available under the
+
+[cmd comm] command. The [cmd {comm init}] method has been retired
+and is replaced by [cmd {comm configure}] which allows access to many
+of the well-defined internal variables. This also generalizes the
+options available to [cmd {comm new}]. Finally, there is now a
+protocol version exchanged when a connection is established. This
+will allow for future on-wire protocol changes. Currently, the
+protocol version is set to 2.
+
+[def 2.3]
+
+[cmd {comm ids}] was renamed to [cmd {comm channels}]. General
+support for [cmd {comm hook}] was fully implemented, but only the
+[term lost] hook exists, and it was changed to follow the general
+hook API. [cmd commLostHook] was unsupported (replaced by
+
+[cmd {comm hook lost}]) and [cmd commLost] was removed.
+
+[def 2.2]
+
+The [term died] hook was renamed [term lost], to be accessed by
+[cmd commLostHook] and an early implementation of
+[cmd {comm lost hook}]. As such, [cmd commDied] is now
+[cmd commLost].
+
+[def 2.1]
+Unsupported method [cmd {comm remoteid}] was added.
+
+[def 2.0]
+[package comm] has been rewritten from scratch (but is fully compatible
+with Comm 1.0, without the requirement to use obTcl).
+
+[list_end]
+
+[include ../common-text/tls-security-notes.inc]
+
+[section Author]
+
+John LoVerso, John@LoVerso.Southborough.MA.US
+
+[para]
+
+[emph http://www.opengroup.org/~loverso/tcl-tk/#comm]
+
+[section License]
+
+Please see the file [emph comm.LICENSE] that accompanied this source,
+or
+[uri http://www.opengroup.org/www/dist_client/caubweb/COPYRIGHT.free.html].
+
+[para]
+
+This license for [package comm], new as of version 3.2, allows it to be
+used for free, without any licensing fee or royalty.
+
+[section Bugs]
+[list_begin itemized]
+[item]
+
+If there is a failure initializing a channel created with
+[cmd {::comm::comm new}], then the channel should be destroyed.
+Currently, it is left in an inconsistent state.
+
+[item]
+
+There should be a way to force a channel to quiesce when changing the
+configuration.
+
+[list_end]
+
+[para]
+The following items can be implemented with the existing hooks and are
+listed here as a reminder to provide a sample hook in a future
+version.
+
+[list_begin itemized]
+[item]
+
+Allow easier use of a slave interp for actual command execution
+(especially when operating in "not local" mode).
+
+[item]
+
+Add host list (xhost-like) or "magic cookie" (xauth-like)
+authentication to initial handshake.
+
+[list_end]
+
+[para]
+The following are outstanding todo items.
+
+[list_begin itemized]
+[item]
+
+Add an interp discovery and name->port mapping. This is likely to be
+in a separate, optional nameserver. (See also the related work,
+below.)
+
+[item]
+
+Fix the [emph {{id host}}] form so as not to be dependent upon
+canonical hostnames. This requires fixes to Tcl to resolve hostnames!
+
+[list_end]
+
+[para]
+This man page is bigger than the source file.
+
+[section {On Using Old Versions Of Tcl}]
+
+[para]
+Tcl7.5 under Windows contains a bug that causes the interpreter to
+hang when EOF is reached on non-blocking sockets. This can be
+triggered with a command such as this:
+
+[para]
+[example {
+ "comm send $other exit"
+}]
+
+[para]
+Always make sure the channel is quiescent before closing/exiting or
+use at least Tcl7.6 under Windows.
+
+[para]
+Tcl7.6 on the Mac contains several bugs. It is recommended you use
+at least Tcl7.6p2.
+
+[para]
+Tcl8.0 on UNIX contains a socket bug that can crash Tcl. It is recommended
+you use Tcl8.0p1 (or Tcl7.6p2).
+
+[section {Related Work}]
+[para]
+Tcl-DP provides an RPC-based remote execution interface, but is a
+compiled Tcl extension. See
+[uri http://www.cs.cornell.edu/Info/Projects/zeno/Projects/Tcl-DP.html].
+
+[para]
+Michael Doyle <miked@eolas.com> has code that implements the Tcl-DP
+RPC interface using standard Tcl sockets, much like [package comm].
+
+[para]
+Andreas Kupries <andreas_kupries@users.sourceforge.net> uses
+[package comm] and has built a simple nameserver as part of his Pool
+library. See [uri http://www.purl.org/net/akupries/soft/pool/index.htm].
+
+[vset CATEGORY comm]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/comm/comm.n.html b/tcllib/modules/comm/comm.n.html
new file mode 100644
index 0000000..03f8a89
--- /dev/null
+++ b/tcllib/modules/comm/comm.n.html
@@ -0,0 +1,1067 @@
+<html>
+<head>
+<!-- This file has been generated by unroff 1.0, 05/30/98 15:43:05. -->
+<!-- Do not edit! -->
+<!-- $Id: comm.n.html,v 1.3 2004/01/15 06:36:12 andreas_kupries Exp $ -->
+<!-- %%_OSF_FREE_COPYRIGHT_%% -->
+<!-- Copyright (C) 1995-1998 The Open Group. All Rights Reserved. -->
+<!-- (Please see the file "comm.LICENSE" that accompanied this source) -->
+<!-- unroff -fhtml -man comm.n -->
+<!-- (then fix &lt;title&gt;) -->
+<!-- # CS - begin code excerpt -->
+<!-- # CE - end code excerpt -->
+<title>Manual page for comm(n) version 3.7.1</title>
+</head>
+<body>
+<h2>
+comm.tcl - A remote communications facility for Tcl (7.6, 8.0, and later)
+<hr></h2>
+<h2>SYNOPSIS</h2>
+<b>package require Comm 3</b>
+<p>
+<!-- define all interfaces ONCE -->
+<!-- iD taken (see i2) -->
+<!-- iE taken (see i6) -->
+<!-- iF taken (see i6) -->
+<!-- Show all interfaces -->
+<b></b><i>chan</i><b> send </b>?<i>-async</i>? <i>id cmd </i>?<i>arg arg ...</i>?<tt> </tt>
+<p>
+<b></b><i>chan</i><b> interps</b>
+<p>
+<b></b><i>chan</i><b> ids</b>
+<p>
+<b></b><i>chan</i><b> self</b>
+<p>
+<b></b><i>chan</i><b> connect </b>?<i>id</i>?<tt> </tt>
+<p>
+<b></b><i>chan</i><b> config
+<br>
+</b><b></b><i>chan</i><b> config </b><i>name</i>
+<br>
+<b></b><i>chan</i><b> config ?</b><i>name value ...</i>?<tt> </tt>
+<br>
+<dl><dt><dd>
+-<b>listen </b>?<i>0|1</i>?<tt> </tt>
+-<b>local </b>?<i>0|1</i>?<tt> </tt>
+-<b>port </b>?<i>port</i>?<tt> </tt>
+</dl>
+<p>
+<b></b><i>chan</i><b> new </b><i>chan</i> ?<i>name value ...</i>?<tt> </tt>
+<p>
+<b></b><i>chan</i><b> channels</b>
+<p>
+<b></b><i>chan</i><b> shutdown </b><i>id</i>
+<p>
+<b></b><i>chan</i><b> abort</b>
+<p>
+<b></b><i>chan</i><b> destroy</b>
+<p>
+<b></b><i>chan</i><b> remoteid</b>
+<p>
+<b></b><i>chan</i><b> hook </b><i>event</i> ?<b>+</b>??<i>script</i>?<tt> </tt>
+<p>
+The package initializes <b>comm</b> as the default <i>chan</i>.<tt> </tt>
+<h2>INTRODUCTION</h2>
+<p>
+The
+<b>comm
+</b>command provides an inter-interpreter remote execution facility
+much like Tk's
+<i>send</i>(n)<i>,
+</i>except that it uses sockets rather than
+the X server for the communication path.<tt> </tt>
+As a result,
+<b>comm
+</b>works with multiple interpreters,
+works on Windows and Macintosh systems,
+and
+provides control over the remote execution path.<tt> </tt>
+<p>
+These commands work just like
+<b>send
+</b>and
+<b>winfo interps</b>:
+<tt></tt><dl><dt><dd>
+<b></b><b>comm</b><b> send </b>?<i>-async</i>? <i>id cmd </i>?<i>arg arg ...</i>?
+<br>
+<b></b><b>comm</b><b> interps</b>
+<br>
+</dl>
+This is all that is really needed to know in order to use
+<b>comm</b>.<tt> </tt>
+<h2>DESCRIPTION</h2>
+<p>
+<b>comm
+</b>names communication endpoints with an
+<i>id
+</i>unique to each machine.<tt> </tt>
+Before sending commands, the
+<i>id
+</i>of another interpreter is needed.<tt> </tt>
+Unlike Tk's send,
+<b>comm
+</b>doesn't implicitly know the
+<i>id</i>'s
+of all the interpreters on the system.<tt> </tt>
+<dl>
+<dt><b></b><b>comm</b><b> send </b>?<i>-async</i>? <i>id cmd </i>?<i>arg arg ...</i>?<tt> </tt>
+<dd>
+This invokes the given command in the interpreter named by
+<i>id</i>.<tt> </tt>
+The command waits for the result and remote errors are returned
+unless the
+<b>-async
+</b>option is given.<tt> </tt>
+<dt><b></b><b>comm</b><b> self</b>
+<dd>
+Returns the
+<i>id
+</i>for this channel.<tt> </tt>
+<dt><b></b><b>comm</b><b> interps</b>
+<dd>
+Returns a list of all the remote
+<i>id</i>'s
+to which this channel is connected.<tt> </tt>
+<b>comm
+</b>learns a new remote
+<i>id
+</i>when a command is first issued it,
+or when a remote
+<i>id
+</i>first issues a command to this comm channel.<tt> </tt>
+<b></b><b>comm</b><b> ids</b>
+is an alias for this method.<tt> </tt>
+<dt><b></b><b>comm</b><b> connect </b>?<i>id</i>?<tt> </tt>
+<dd>
+Whereas
+<b>comm send
+</b>will automatically connect to the given
+<i>id</i>,
+this forces a connection to a remote
+<i>id
+</i>without sending a command.<tt> </tt>
+After this, the remote
+<i>id
+</i>will appear in
+<b>comm interps</b>.<tt> </tt>
+</dl>
+<p>
+These four methods make up the basic
+<b>comm
+</b>interface.<tt> </tt>
+<h2>EVAL SEMANTICS</h2>
+<p>
+The evaluation semantics of
+<b>comm send
+</b>are intended to match Tk's
+<b>send
+</b><i>exactly</i>.<tt> </tt>
+This means that
+<b>comm
+</b>evaluates arguments on the remote side.<tt> </tt>
+<p>
+If you find that
+<b>comm send
+</b>doesn't work for a particular command,
+try the same thing with Tk's send and see if the result is different.<tt> </tt>
+If there is a problem, please report it.<tt> </tt>
+For instance, there was had one report that this command produced an error.<tt> </tt>
+Note that the equivalent
+<b>send
+</b>command also produces the same error.<tt> </tt>
+<tt></tt><dl><dt><dd>
+% <b>comm send </b><i>id</i><b> llength {a b c}</b>
+<br>
+<b>wrong # args: should be "llength list"</b>
+<br>
+% <b>send </b><i>name</i><b> llength {a b c}</b>
+<br>
+<b>wrong # args: should be "llength list"</b>
+<br>
+</dl>
+<p>
+The
+<b>eval
+</b>hook (described below) can be used to change from
+<b>send</b>'s
+double eval semantics to single eval semantics.<tt> </tt>
+<h2>MULTIPLE CHANNELS</h2>
+<p>
+More than one
+<b>comm
+</b>channel (or
+<i>listener</i>)
+can be created in each Tcl interpeter.<tt> </tt>
+This allows flexibility to create full and restricted channels.<tt> </tt>
+For instance,
+<b>hook
+</b>scripts are specific to the channel they are defined against.
+<dl>
+<dt><b></b><b>comm</b><b> new </b><i>chan</i> ?<i>name value ...</i>?<tt> </tt>
+<dd>
+This creates a new channel and Tcl command with the given channel name.<tt> </tt>
+This new command controls the new channel and takes all the same
+arguments as
+<b>comm</b>.<tt> </tt>
+Any remaining arguments are passed to the
+<b>config
+</b>method.<tt> </tt>
+<dt><b></b><b>comm</b><b> channels</b>
+<dd>
+This lists all the channels allocated in this Tcl interpreter.<tt> </tt>
+</dl>
+<p>
+The default configuration parameters for a new channel are:
+<tt></tt><dl><dt><dd>
+<b>-port 0 -local 1 -listen 0
+</b></dl>
+The default channel
+<b>comm
+</b>is created with:
+<tt></tt><dl><dt><dd>
+<b>comm new comm -port 0 -local 1 -listen 1
+</b></dl>
+<h2>CHANNEL CONFIGURATION</h2>
+<p>
+The
+<b>config
+</b>method acts similar to
+<b>fconfigure
+</b>in that it sets or queries configuration variables associated with a channel.<tt> </tt>
+<dl><dt><dd>
+<b></b><b>comm</b><b> config
+<br>
+</b><b></b><b>comm</b><b> config </b><i>name</i>
+<br>
+<b></b><b>comm</b><b> config ?</b><i>name value ...</i>?<tt> </tt>
+</dl>
+When given no arguments,
+<b>config
+</b>returns a list of all variables and their value
+With one argument,
+<b>config
+</b>returns the value of just that argument.<tt> </tt>
+With an even number of arguments, the given variables are set to the
+given values.<tt> </tt>
+<p>
+These configuration variables can be changed
+(descriptions of them are elsewhere in this manual page):
+<dl><dt><dd>
+-<b>listen </b>?<i>0|1</i>?<tt> </tt>
+-<b>local </b>?<i>0|1</i>?<tt> </tt>
+-<b>port </b>?<i>port</i>?<tt> </tt>
+</dl>
+<p>
+These configuration variables are readonly:
+<dl><dt><dd>
+-<b>chan</b> <i>chan</i>
+-<b>serial</b> <i>n</i>
+-<b>socket</b> sock<i>n</i>
+</dl>
+<p>
+When
+<b>config
+</b>changes the parameters of an existing channel,
+it closes and reopens the listening socket.<tt> </tt>
+An automatically assigned channel
+<i>id
+</i>will change when this happens.<tt> </tt>
+Recycling the socket is done by invoking
+<b>comm abort</b>,
+which causes all active sends to terminate.<tt> </tt>
+<h2>ID/PORT ASSIGNMENTS</h2>
+<p>
+<b>comm
+</b>uses a TCP port for endpoint
+<i>id</i>.<tt> </tt>
+The
+<b>interps
+</b>(or
+<b>ids</b>)
+method merely lists all the TCP ports to which the channel is connected.<tt> </tt>
+By default, each channel's
+<i>id
+</i>is randomly assigned by the operating system
+(but usually starts at a low value around 1024 and increases
+each time a new socket is opened).<tt> </tt>
+This behavior is accomplished by giving the
+<b>-port
+</b>config option a value of 0.<tt> </tt>
+Alternately, a specific TCP port number may be provided for a given channel.<tt> </tt>
+As a special case, comm contains code to allocate a
+a high-numbered TCP port (&gt;10000) by using
+<b>-port {}</b>.<tt> </tt>
+Note that a channel won't be created and initialized
+unless the specific port can be allocated.<tt> </tt>
+<p>
+As a special case, if the channel is configured with
+<b>-listen 0</b>,
+then it will not create a listening socket and will use an id of
+<i>0
+</i>for itself.<tt> </tt>
+Such a channel is only good for outgoing connections
+(although once a connection is established, it can carry send traffic
+in both directions).<tt> </tt>
+<h2>REMOTE INTERPRETERS</h2>
+<p>
+By default, each channel is restricted to accepting connections from the
+local system. This can be overriden by using the
+<b>-local 0
+</b>configuration option
+For such channels, the
+<i>id
+</i>parameter takes the form
+<b>{</b><i>id host</i><b>}
+</b><b></b>.<tt> </tt>
+<p>
+<b>WARNING</b>:
+The
+<i>host
+</i>must always be specified in the same form
+(e.g., as either a fully qualified domain name,
+plain hostname or an IP address).<tt> </tt>
+<h2>CLOSING CONNECTIONS</h2>
+<p>
+These methods give control over closing connections:
+<dl>
+<dt><b></b><b>comm</b><b> shutdown </b><i>id</i>
+<dd>
+This closes the connection to
+<i>id</i>,
+aborting all outstanding commands in progress. Note that nothing
+prevents the connection from being immediately reopened by another
+incoming or outgoing command.<tt> </tt>
+<dt><b></b><b>comm</b><b> abort</b>
+<dd>
+This invokes shutdown on all open connections in this comm channel.<tt> </tt>
+<dt><b></b><b>comm</b><b> destroy</b>
+<dd>
+This aborts all connections and then destroys the this comm channel itself,
+including closing the listening socket.<tt> </tt>
+Special code allows the default
+<b>comm
+</b>channel to be closed
+such that the
+<b>comm
+</b>command it is not destroyed.<tt> </tt>
+Doing so closes the listening socket, preventing both
+incoming and outgoing commands on the channel.<tt> </tt>
+This sequence reinitializes the default channel:
+<tt></tt></dl>
+<dl><dt><dd>
+<b>comm destroy; comm new comm
+</b></dl>
+<p>
+When a remote connection is lost (because the remote exited or called
+<b>shutdown</b>),
+<b>comm
+</b>can invoke an application callback.<tt> </tt>
+This can be used to cleanup or restart an ancillary process,
+for instance.<tt> </tt>
+See the
+<b>lost
+</b>callback below.<tt> </tt>
+<h2>CALLBACKS</h2>
+<p>
+This is a mechanism for setting hooks for particular events:
+<tt></tt><dl><dt><dd>
+<b></b><b>comm</b><b> hook </b><i>event</i> ?<b>+</b>??<i>script</i>?
+<br>
+</dl>
+<p>
+This uses a syntax similar to Tk's
+<b>bind
+</b>command.<tt> </tt>
+Prefixing
+<i>script
+</i>with a + causes the new script to be appended.<tt> </tt>
+Without this, a new
+<i>script
+</i>replaces any existing script.<tt> </tt>
+When invoked without a script, no change is made.<tt> </tt>
+In all cases, the new hook script is returned by the command.<tt> </tt>
+<p>
+When an
+<i>event
+</i>occurs,
+the
+<i>script
+</i>associated with it is evaluated
+with the listed variables in scope and available.<tt> </tt>
+The return code
+(<b>not
+</b>the return value) of the script
+is commonly used decide how to further process after the hook.<tt> </tt>
+<p>
+Common variables include:
+<dl><dt><dd>
+<dl>
+<dt><b>chan</b><dd>
+the name of the comm channel (and command)
+<dt><b>id</b><dd>
+the id of the remote in question
+<dt><b>fid</b><dd>
+the file id for the socket of the connection
+</dl>
+</dl>
+
+
+These are the defined
+<i>events</i>:
+<dl>
+<dt><b>connecting
+</b><dd>
+Variables:
+<i>chan id host port
+</i><br>
+This hook is invoked before making a connection
+to the remote named in
+<i>id</i>.<tt> </tt>
+An error return (via
+<b>error</b>)
+will abort the connection attempt with the error.<tt> </tt>
+Example:
+<p>
+<tt></tt></dl>
+<dl><dt><dd>
+% comm hook connecting {
+<br>
+ if [string match {*[02468]} $id] {
+<br>
+ error "Can't connect to even ids"
+<br>
+ }
+<br>
+}
+<br>
+% comm send 10000 puts ok
+<br>
+Connect to remote failed: Can't connect to even ids
+<br>
+%
+<br>
+</dl>
+
+<dl>
+<dt><b>connected
+</b><dd>
+Variables:
+<i>chan fid id host port
+</i><br>
+This hook is invoked immediately after making a remote connection to
+<i>id</i>,
+allowing arbitrary authentication over the socket
+named by
+<i>fid</i>.<tt> </tt>
+An error return (via
+<b>error</b>)
+will close the connection with the error.<tt> </tt>
+<i>host
+</i>and
+<i>port
+</i>are merely extracted from the
+<i>id</i>;
+changing any of these will have no effect on the connection, however.<tt> </tt>
+It is also possible to substitute and replace
+<i>fid .
+</i>
+
+<dt><b>incoming
+</b><dd>
+Variables:
+<i>chan fid addr remport
+</i><br>
+Hook invoked when receiving an incoming connection,
+allowing arbitrary authentication over socket
+named by
+<i>fid</i>.<tt> </tt>
+An error return (via
+<b>error</b>)
+will close the connection with the error.<tt> </tt>
+Note that the peer is named by
+<i>remport</i> and <i>addr
+</i>but that the remote
+<i>id
+</i>is still unknown. Example:
+<p>
+<tt></tt></dl>
+<dl><dt><dd>
+comm hook incoming {
+<br>
+ if [string match 127.0.0.1 $addr] {
+<br>
+ error "I don't talk to myself"
+<br>
+ }
+<br>
+}
+<br>
+</dl>
+
+<dl>
+<dt><b>eval
+</b><dd>
+Variables:
+<i>chan id cmd buffer
+</i><br>
+This hook is invoked after collecting a complete script from a remote
+but
+<b>before
+</b>evalutating it.<tt> </tt>
+This allows complete control over the processing of incoming commands.<tt> </tt>
+<i>cmd
+</i>contains either
+<b>send</b> or <b>async</b>.<tt> </tt>
+<i>buffer
+</i>holds the script to evaluate.<tt> </tt>
+At the time the hook is called,
+<b>$chan remoteid
+</b>is identical in value to
+<b>id.
+</b><p>
+By changing
+<i>buffer</i>,
+the hook can change the script to be evaluated.<tt> </tt>
+The hook can short circuit evaluation and cause a
+value to be immediately returned by using
+<b>return
+</b><i>result
+</i>(or, from within a procedure,
+<b>return -code return
+</b><i>result</i>).<tt> </tt>
+An error return (via
+<b>error</b>)
+will return an error result, as is if the script caused the error.<tt> </tt>
+Any other return will evaluate the script in
+<i>buffer
+</i>as normal.<tt> </tt>
+For compatibility with 3.2,
+<b>break
+</b>and
+<b>return -code break
+</b><i>result
+</i>is supported, acting similarly to
+<b>return {}
+</b>and
+<b>return -code return
+</b><i>result</i>.<tt> </tt>
+<p>
+Examples:
+</dl>
+<dl><dt><dd>
+1. augmenting a command
+<tt></tt><dl><dt><dd>
+% comm send [comm self] pid
+<br>
+5013
+<br>
+% comm hook eval {puts "going to execute $buffer"}
+<br>
+% comm send [comm self] pid
+<br>
+going to execute pid
+<br>
+5013
+<br>
+</dl>
+2. short circuting a command
+<tt></tt><dl><dt><dd>
+% comm hook eval {puts "would have executed $buffer"; return 0}
+<br>
+% comm send [comm self] pid
+<br>
+would have executed pid
+<br>
+0
+<br>
+</dl>
+3. Replacing double eval semantics
+<tt></tt><dl><dt><dd>
+% comm send [comm self] llength {a b c}
+<br>
+wrong # args: should be "llength list"
+<br>
+% comm hook eval {return [uplevel #0 $buffer]}
+<br>
+return [uplevel #0 $buffer]
+<br>
+% comm send [comm self] llength {a b c}
+<br>
+3
+<br>
+</dl>
+4. Using a slave interpreter
+<tt></tt><dl><dt><dd>
+% interp create foo
+<br>
+% comm hook eval {return [foo eval $buffer]}
+<br>
+% comm send [comm self] set myvar 123
+<br>
+123
+<br>
+% set myvar
+<br>
+can't read "myvar": no such variable
+<br>
+% foo eval set myvar
+<br>
+123
+<br>
+</dl>
+5. Using a slave interpreter (double eval)
+<tt></tt><dl><dt><dd>
+% comm hook eval {return [eval foo eval $buffer]}
+<br>
+</dl>
+6. Subverting the script to execute
+<tt></tt><dl><dt><dd>
+% comm hook eval {
+<br>
+ switch -- $buffer {
+<br>
+ a {return A-OK} b {return B-OK} default {error "$buffer is a no-no"}
+<br>
+ }
+<br>
+}
+<br>
+% comm send [comm self] pid
+<br>
+pid is a no-no
+<br>
+% comm send [comm self] a
+<br>
+A-OK
+<br>
+</dl>
+</dl>
+
+<dl>
+<dt><b>reply
+</b><dd>
+Variables:
+<i>chan id buffer ret return()
+</i><br>
+This hook is invoked after collecting a complete reply script from a remote
+but
+<b>before
+</b>evalutating it.<tt> </tt>
+This allows complete control over the processing of replies to sent commands.<tt> </tt>
+The reply
+<i>buffer
+</i>is in one of the following forms
+</dl>
+<dl><dt><dd>
+<tt></tt><dl><dt><dd>
+return <i>result</i>
+<br>
+return -code <i>code</i> <i>result</i>
+<br>
+return -code <i>code</i> -errorinfo <i>info</i> -errorcode <i>ecode</i> <i>msg</i>
+<br>
+</dl>
+For safety reasons, this is decomposed. The return result
+is in
+<i>ret</i>,
+and the return switches are in the return array:
+<tt></tt><dl><dt><dd>
+<i>return(-code)
+</i><i>return(-errorinfo)
+</i><i>return(-errordcode)
+</i></dl>
+Any of these may be the empty string.<tt> </tt>
+Modifying
+these four variables can change the return value, whereas
+modifying
+<i>buffer
+</i>has no effect.<tt> </tt>
+</dl>
+
+<dl>
+<dt><b>lost
+</b><dd>
+Variables:
+<i>chan id reason
+</i><br>
+This hook is invoked when the connection to
+<i>id
+</i>is lost.<tt> </tt>
+Return value (or thrown error) is ignored.<tt> </tt>
+<i>reason
+</i>is an explanatory string indicating why the connection was lost.<tt> </tt>
+Example:
+<p>
+<tt></tt></dl>
+<dl><dt><dd>
+comm hook lost {
+<br>
+ global myvar
+<br>
+ if {$myvar(id) == $id} {
+<br>
+ myfunc
+<br>
+ return
+<br>
+ }
+<br>
+}
+<br>
+</dl>
+<h2>UNSUPPORTED</h2>
+<p>
+These interfaces may change or go away in subsequence releases.<tt> </tt>
+<dl>
+<dt><b></b><b>comm</b><b> remoteid</b>
+<dd>
+Returns the
+<i>id
+</i>of the sender of the last remote command executed on this channel.<tt> </tt>
+If used by a proc being invoked remotely, it
+must be called before any events are processed.<tt> </tt>
+Otherwise, another command may get invoked and change the value.<tt> </tt>
+<dt><b>comm_send
+</b><dd>
+Invoking this procedure will substitute the Tk
+<b>send
+</b>and
+<b>winfo interps
+</b>commands with these equivalents that use
+<b>comm</b>.<tt> </tt>
+<p>
+<tt></tt></dl>
+<dl><dt><dd>
+proc send {args} {
+<br>
+ eval comm send $args
+<br>
+}
+<br>
+rename winfo tk_winfo
+<br>
+proc winfo {cmd args} {
+<br>
+ if ![string match in* $cmd] {return [eval [list tk_winfo $cmd] $args]}
+<br>
+ return [comm interps]
+<br>
+}
+<br>
+</dl>
+<h2>SECURITY</h2>
+<p>
+Something here soon.<tt> </tt>
+<h2>BLOCKING SEMANTICS</h2>
+<p>
+There is one outstanding difference between
+<b>comm
+</b>and
+<b>send</b>.<tt> </tt>
+When blocking in a synchronous remote command,
+<b>send
+</b>uses an internal C hook (Tk_RestrictEvents)
+to the event loop to look ahead for
+send-related events and only process those without processing any other events.<tt> </tt>
+In contrast,
+<b>comm
+</b>uses the
+<b>vwait
+</b>command as a semaphore to indicate the return message has arrived.<tt> </tt>
+The difference is that a synchornous
+<b>send
+</b>will block the application and prevent all events
+(including window related ones) from being processed,
+while a synchronous
+<b>comm
+</b>will block the application but still allow
+other events will still get processed.<tt> </tt>
+In particular,
+<b>after idle
+</b>handlers will fire immediately when comm blocks.<tt> </tt>
+<p>
+What can be done about this?<tt> </tt>
+First, note that this behavior will come from any code using
+<b>vwait
+</b>to block and wait for an event to occur.<tt> </tt>
+At the cost of multiple channel support,
+<b>comm
+</b>could be changed to do blocking I/O on the socket,
+givng send-like blocking semantics.<tt> </tt>
+However, multiple channel support is a very useful feature of comm
+that it is deemed too important to lose.<tt> </tt>
+The remaining approaches involve a new loadable module written in C
+(which is somewhat against the philosophy of
+<b>comm</b>)
+One way would be to create a modified version of the
+<b>vwait
+</b>command that allow the event flags passed to Tcl_DoOneEvent to be specified.<tt> </tt>
+For
+<b>comm</b>,
+just the TCL_FILE_EVENTS would be processed.<tt> </tt>
+Another way would be to implement a mechanism like Tk_RestrictEvents, but
+apply it to the Tcl event loop (since
+<b>comm
+</b>doesn't require Tk).<tt> </tt>
+One of these approaches will be available in a future
+<b>comm
+</b>release as an optional component.<tt> </tt>
+<h2>COMPATIBILITY</h2>
+<p>
+<b>Comm
+</b>exports itself as a package.<tt> </tt>
+The package version number is in the form
+<i>major</i>.<i>minor</i>,
+where the major version will only change when
+a non-compatible change happens to the API or protocol.<tt> </tt>
+Minor bug fixes and changes will only affect the minor version.<tt> </tt>
+To load
+<b>comm
+</b>this command is usually used:
+<tt></tt><dl><dt><dd>
+<b>package require Comm 3</b>
+<br>
+</dl>
+Note that requiring no version (or a specific version) can also be done.<tt> </tt>
+<p>
+The revision history of
+<b>comm
+</b>includes these releases:
+
+<dl>
+<dt>3.6<dd>
+A bug in the looking up of the remoteid for a executed command
+could be triggered when the connection was closed while several
+asynchronous sends were queued to be executed.<tt> </tt>
+
+<dt>3.5<dd>
+Internal change to how reply messages from a
+<b>send
+</b>are handled.<tt> </tt>
+Reply messages are now decoded into the
+<i>value
+</i>to pass to
+<b>return</b>;
+a new return statement is then cons'd up to with this value.<tt> </tt>
+Previously, the return code was passed in from the remote as a
+command to evaluate. Since the wire protocol has not changed,
+this is still the case. Instead, the reply handling code decodes the
+<b>reply
+</b>message.<tt> </tt>
+
+<dt>3.4<dd>
+Added more source commentary, as well as documenting config variables
+in this man page.<tt> </tt>
+Fixed bug were loss of connection would give error about a variable
+named
+rather than the message about the lost connection.<tt> </tt>
+<b>comm ids
+</b>is now an alias for
+<b>comm interps
+</b>(previously, it an alias for
+<b>comm chans</b>).<tt> </tt>
+Since the method invocation change of 3.0, break and other exceptional
+conditions were not being returned correctly from
+<b>comm send</b>.<tt> </tt>
+This has been fixed by removing the extra level of indirection into
+the internal procedure
+<b>commSend</b>.<tt> </tt>
+Also added propogation of the
+<i>errorCode
+</i>variable.<tt> </tt>
+This means that these commands return exactly as they would with
+<b>send</b>:
+</dl>
+<dl><dt><dd>
+<tt></tt><dl><dt><dd>
+comm send <i>id</i> break
+<br>
+catch {comm send <i>id</i> break}
+<br>
+comm send <i>id</i> expr 1 / 0
+<br>
+</dl>
+Added a new hook for reply messages.<tt> </tt>
+Reworked method invocation to avoid the use of comm:* procedures;
+this also cut the invocation time down by 40%.<tt> </tt>
+Documented
+<b>comm config
+</b>(as this manual page still listed the defunct
+<b>comm init</b>!)
+</dl>
+
+<dl>
+<dt>3.3<dd>
+Some minor bugs were corrected and the documentation was cleaned up.<tt> </tt>
+Added some examples for hooks. The return semantics of the
+<b>eval
+</b>hook were changed.<tt> </tt>
+
+<dt>3.2<dd>
+A new wire protocol, version 3, was added. This is backwards compatible
+with version 2 but adds an exchange of supported protocol versions to
+allow protocol negotiation in the future.<tt> </tt>
+Several bugs with the hook implementation were fixed.<tt> </tt>
+A new section of the man page on blocking semantics was added.<tt> </tt>
+
+<dt>3.1<dd>
+All the documented hooks were implemented.<tt> </tt>
+<b>commLostHook
+</b>was removed.<tt> </tt>
+A bug in
+<b>comm new
+</b>was fixed.<tt> </tt>
+
+<dt>3.0<dd>
+This is a new version of
+<b>comm
+</b>with several major changes.<tt> </tt>
+There is a new way of creating the methods available under the
+<b>comm
+</b>command.<tt> </tt>
+The
+<b>comm init
+</b>method has been retired and is replaced by
+<b>comm configure
+</b>which allows access to many of the well-defined internal variables.<tt> </tt>
+This also generalizes the options available to
+<b>comm new</b>.<tt> </tt>
+Finally, there is now a protocol version exchanged when a connection
+is established. This will allow for future on-wire protocol changes.<tt> </tt>
+Currently, the protocol version is set to 2.<tt> </tt>
+
+<dt>2.3<dd>
+<b>comm ids
+</b>was renamed to
+<b>comm channels .
+</b>General support for
+<b>comm hook
+</b>was fully implemented, but
+only the
+<b>lost
+</b>hook exists, and it was changed to follow the general hook API.<tt> </tt>
+<b>commLostHook
+</b>was unsupported (replaced by
+<b>comm hook lost )
+</b>and
+<b>commLost
+</b>was removed.<tt> </tt>
+
+<dt>2.2<dd>
+The
+<b>died
+</b>hook was renamed
+<b>lost</b>,
+to be accessed by
+<b>commLostHook
+</b>and an early implementation of
+<b>comm lost hook</b>.<tt> </tt>
+As such,
+<b>commDied
+</b>is now
+<b>commLost</b>.<tt> </tt>
+
+<dt>2.1<dd>
+Unsupported method
+<b>comm remoteid
+</b>was added.<tt> </tt>
+
+<dt>2.0<dd>
+<b>comm
+</b>has been rewritten from scratch (but is fully compatible with Comm 1.0,
+without the requirement to use obTcl).<tt> </tt>
+</dl>
+<h2>SEE ALSO</h2>
+<i>send</i>(n)
+<h2>AUTHOR</h2>
+John LoVerso, John@LoVerso.Southborough.MA.US
+<p>
+<i>http://www.opengroup.org/~loverso/tcl-tk/#comm
+</i><h2>COPYRIGHT</h2>
+Copyright (C) 1995-1998 The Open Group. All Rights Reserved.<tt> </tt>
+Please see the file
+<i>comm.LICENSE
+</i>that accompanied this source,
+or
+<i>http://www.opengroup.org/www/dist_client/caubweb/COPYRIGHT.free.html</i>.<tt> </tt>
+<p>
+This license for
+<b>comm</b>,
+new as of version 3.2,
+allows it to be used for free,
+without any licensing fee or royalty.<tt> </tt>
+<h2>BUGS</h2>
+<ul>
+<li>
+If there is a failure initializing a channel created with
+<b>comm new</b>,
+then the channel should be destroyed.<tt> </tt>
+Currently, it is left in an inconsistent state.<tt> </tt>
+<li>
+There should be a way to force a channel to quiesce when changing the
+configuration.<tt> </tt>
+</ul>
+<p>
+The following items can be implemented with the existing hooks
+and are listed here as a reminder to provide a sample hook in a future version.<tt> </tt>
+<ul>
+<li>
+Allow easier use of a slave interp for actual command execution
+(especially when operating in "not local" mode).<tt> </tt>
+<li>
+Add host list (xhost-like) or "magic cookie" (xauth-like)
+authentication to initial handshake.<tt> </tt>
+</ul>
+<p>
+The following are outstanding todo items.<tt> </tt>
+<ul>
+<li>
+Add an interp discovery and name-&gt;port mapping.<tt> </tt>
+This is likely to be in a separate, optional nameserver.<tt> </tt>
+(See also the related work, below.)
+<li>
+Fix the
+<i>{id host}
+</i>form so as not to be dependent upon canonical hostnames.<tt> </tt>
+This requires fixes to Tcl to resolve hostnames!<tt> </tt>
+</ul>
+<p>
+<p>
+<p>
+This man page is bigger than the source file.<tt> </tt>
+<h2>ON USING OLD VERSIONS OF TCL</h2>
+<p>
+Tcl7.5 under Windows contains a bug that causes the interpreter to
+hang when EOF is reached on non-blocking sockets. This can be
+triggered with a command such as this:
+<tt></tt><dl><dt><dd>
+<b>comm send $other exit
+</b></dl>
+Always make sure the channel is quiescent before closing/exiting or
+use at least Tcl7.6 under Windows.<tt> </tt>
+<p>
+Tcl7.6 on the Mac contains several bugs. It is recommended you use
+at least Tcl7.6p2.<tt> </tt>
+<p>
+Tcl8.0 on UNIX contains a socket bug that can crash Tcl. It is recommended
+you use Tcl8.0p1 (or Tcl7.6p2).<tt> </tt>
+<h2>RELATED WORK</h2>
+<p>
+Tcl-DP provides an RPC-based remote execution interface, but is a compiled
+Tcl extension. See
+<i>http://www.cs.cornell.edu/Info/Projects/zeno/Projects/Tcl-DP.html</i>.<tt> </tt>
+<p>
+Michael Doyle &lt;miked@eolas.com&gt; has code that implements the Tcl-DP RPC
+interface using standard Tcl sockets, much like
+<b>comm</b>.<tt> </tt>
+<p>
+Andreas Kupries &lt;a.kupries@westend.com&gt; uses
+<b>comm
+</b>and has built a simple nameserver as part of his Pool library.<tt> </tt>
+See
+<i>http://www.westend.com/~kupries/doc/pool/index.htm</i>.<tt> </tt>
+<!-- eof -->
+<p><hr>
+Markup created by <em>unroff</em> 1.0,&#160;<tt> </tt>&#160;<tt> </tt>May 30, 1998.
+</body>
+</html>
diff --git a/tcllib/modules/comm/comm.pcx b/tcllib/modules/comm/comm.pcx
new file mode 100644
index 0000000..86a1dd0
--- /dev/null
+++ b/tcllib/modules/comm/comm.pcx
@@ -0,0 +1,99 @@
+# -*- tcl -*- comm.pcx
+# Syntax of the commands provided by package comm.
+
+# For use by TclDevKit's static syntax checker.
+# See http://www.activestate.com/solutions/tcl/
+# See http://aspn.activestate.com/ASPN/docs/Tcl_Dev_Kit/4.0/Checker.html#pcx_api
+# for the documentation describing the format of the code contained in this file
+#
+
+package require pcx
+pcx::register comm
+pcx::tcldep 4.5.7 needs tcl 8.3
+
+namespace eval ::comm {}
+
+#pcx::message FOO {... text ...} type
+#pcx::scan <VERSION> <NAME> <RULE>
+
+# TODO: new, word = name of comm channel object, register in scan mode, using this syntax.
+# TODO: hook, word = bind script, look at tk bind syntax.
+
+pcx::check 4.5.7 std ::comm::comm \
+ {checkSimpleArgs 1 -1 {
+ {checkOption {
+ {abort {checkSimpleArgs 0 0 {}}}
+ {channels {checkSimpleArgs 0 0 {}}}
+ {configure {checkSimpleArgs 0 -1 {
+ comm::checkCommSwitches
+ }}}
+ {connect {checkSimpleArgs 1 1 {
+ comm::checkCommId
+ }}}
+ {debug {checkSimpleArgs 1 1 {
+ checkBoolean
+ }}}
+ {destroy {checkSimpleArgs 0 0 {}}}
+ {hook {checkSimpleArgs 1 2 {
+ comm::checkCommHook
+ checkWord
+ }}}
+ {ids {checkSimpleArgs 0 0 {}}}
+ {interps {checkSimpleArgs 0 0 {}}}
+ {new {checkSimpleArgs 1 -1 {
+ checkWord
+ comm::checkCommSwitches
+ }}}
+ {remoteid {checkSimpleArgs 0 0 {}}}
+ {return_async {checkSimpleArgs 0 0 {}}}
+ {self {checkSimpleArgs 0 0 {}}}
+ {send {checkSimpleArgs 1 -1 {
+ {checkSwitches 1 {
+ -async
+ {-command {checkWord}}
+ } {checkSimpleArgs 2 -1 {
+ comm::checkCommId
+ checkEvalArgs
+ }}}
+ }}}
+ {shutdown {checkSimpleArgs 1 1 {comm::checkCommId}}}
+ } {}}
+ }}
+pcx::check 4.5.7 std ::comm::comm_send \
+ {checkSimpleArgs 0 0 {}}
+
+# Initialization via pcx::init.
+# Use a ::comm::init procedure for non-standard initialization.
+
+proc comm::checkCommId {t i} {
+ return [checkListValues 1 2 {
+ checkWholeNum
+ checkWord
+ } $t $i]
+}
+
+proc comm::checkCommHook {t i} {
+ return [checkKeyword 1 {
+ connecting connected incoming eval callback reply lost
+ } $t $i]
+}
+
+proc comm::checkCommSwitches {t i} {
+ # socket, serial, encoding are read-only, hence the check that
+ # they are used only without an argument.
+ return [checkSwitches 1 {
+ {-chan {checkSimpleArgs 0 0 {}}}
+ {-encoding checkWord}
+ {-events {checkListValues 0 -1 {comm::checkCommHook}}}
+ {-interp checkWord}
+ {-listen checkBoolean}
+ {-local checkBoolean}
+ {-port checkWholeNum}
+ {-serial {checkSimpleArgs 0 0 {}}}
+ {-silent checkBoolean}
+ {-socket {checkSimpleArgs 0 0 {}}}
+ {-socketcmd checkWord}
+ } {} $t $i]
+}
+
+pcx::complete
diff --git a/tcllib/modules/comm/comm.slaveboot b/tcllib/modules/comm/comm.slaveboot
new file mode 100644
index 0000000..e70bcf9
--- /dev/null
+++ b/tcllib/modules/comm/comm.slaveboot
@@ -0,0 +1,42 @@
+# -*- tcl -*-
+# Script to boot a child running an open comm server
+
+set spawncode [makeFile {
+ catch {wm withdraw .}
+ ##puts [set fh [open ~/foo w]] $argv ; close $fh
+
+ source [lindex $argv 0] ; # load 'snit'
+ source [lindex $argv 1].tcl ; # load 'comm'
+ # and wait for commands. But first send our
+ # own server socket to the initiator
+ ::comm::comm send [lindex $argv 2] [list slaveat [::comm::comm self]]
+ vwait forever
+} spawn]
+
+proc slaveat {id} {
+ #puts "Slave @ $id"
+ proc slave {} [list return $id]
+ set ::go .
+}
+
+#puts "self @ [::comm::comm self]"
+
+exec \
+ [info nameofexecutable] $spawncode \
+ [tcllibPath snit/snit.tcl] \
+ [file rootname [info script]] \
+ [::comm::comm self] &
+
+#puts "Waiting for spawned comm system to boot"
+# Wait for the slave to initialize itself.
+vwait ::go
+
+#puts "Running tests"
+#::comm::comm debug 1
+
+proc slavestop {} {
+ ::comm::comm send -async [slave] {{exit}}
+ ::comm::comm abort
+ removeFile spawn
+ return
+}
diff --git a/tcllib/modules/comm/comm.tcl b/tcllib/modules/comm/comm.tcl
new file mode 100644
index 0000000..55eda34
--- /dev/null
+++ b/tcllib/modules/comm/comm.tcl
@@ -0,0 +1,1818 @@
+# comm.tcl --
+#
+# socket-based 'send'ing of commands between interpreters.
+#
+# %%_OSF_FREE_COPYRIGHT_%%
+# Copyright (C) 1995-1998 The Open Group. All Rights Reserved.
+# (Please see the file "comm.LICENSE" that accompanied this source,
+# or http://www.opengroup.org/www/dist_client/caubweb/COPYRIGHT.free.html)
+# Copyright (c) 2003-2007 ActiveState Corporation
+#
+# This is the 'comm' package written by Jon Robert LoVerso, placed
+# into its own namespace during integration into tcllib.
+#
+# Note that the actual code was changed in several places (Reordered,
+# eval speedup)
+#
+# comm works just like Tk's send, except that it uses sockets.
+# These commands work just like "send" and "winfo interps":
+#
+# comm send ?-async? <id> <cmd> ?<arg> ...?
+# comm interps
+#
+# See the manual page comm.n for further details on this package.
+#
+# RCS: @(#) $Id: comm.tcl,v 1.34 2010/09/15 19:48:33 andreas_kupries Exp $
+
+package require Tcl 8.3
+package require snit ; # comm::future objects.
+
+namespace eval ::comm {
+ namespace export comm comm_send
+
+ variable comm
+ array set comm {}
+
+ if {![info exists comm(chans)]} {
+ array set comm {
+ debug 0 chans {} localhost 127.0.0.1
+ connecting,hook 1
+ connected,hook 1
+ incoming,hook 1
+ eval,hook 1
+ callback,hook 1
+ reply,hook 1
+ lost,hook 1
+ offerVers {3 2}
+ acceptVers {3 2}
+ defVers 2
+ defaultEncoding "utf-8"
+ defaultSilent 0
+ }
+ set comm(lastport) [expr {[pid] % 32768 + 9999}]
+ # fast check for acceptable versions
+ foreach comm(_x) $comm(acceptVers) {
+ set comm($comm(_x),vers) 1
+ }
+ catch {unset comm(_x)}
+ }
+
+ # Class variables:
+ # lastport saves last default listening port allocated
+ # debug enable debug output
+ # chans list of allocated channels
+ # future,fid,$fid List of futures a specific peer is waiting for.
+ #
+ # Channel instance variables:
+ # comm()
+ # $ch,port listening port (our id)
+ # $ch,socket listening socket
+ # $ch,socketcmd command to use to create sockets.
+ # $ch,silent boolean to indicate whether to throw error on
+ # protocol negotiation failure
+ # $ch,local boolean to indicate if port is local
+ # $ch,interp interpreter to run received scripts in.
+ # If not empty we own it! = We destroy it
+ # with the channel
+ # $ch,events List of hoks to run in the 'interp', if defined
+ # $ch,serial next serial number for commands
+ #
+ # $ch,hook,$hook script for hook $hook
+ #
+ # $ch,peers,$id open connections to peers; ch,id=>fid
+ # $ch,fids,$fid reverse mapping for peers; ch,fid=>id
+ # $ch,vers,$id negotiated protocol version for id
+ # $ch,pending,$id list of outstanding send serial numbers for id
+ #
+ # $ch,buf,$fid buffer to collect incoming data
+ # $ch,result,$serial result value set here to wake up sender
+ # $ch,return,$serial return codes to go along with result
+
+ if {0} {
+ # Propagate result, code, and errorCode. Can't just eval
+ # otherwise TCL_BREAK gets turned into TCL_ERROR.
+ global errorInfo errorCode
+ set code [catch [concat commSend $args] res]
+ return -code $code -errorinfo $errorInfo -errorcode $errorCode $res
+ }
+}
+
+# ::comm::comm_send --
+#
+# Convenience command. Replaces Tk 'send' and 'winfo' with
+# versions using the 'comm' variants. Multiple calls are
+# allowed, only the first one will have an effect.
+#
+# Arguments:
+# None.
+#
+# Results:
+# None.
+
+proc ::comm::comm_send {} {
+ proc send {args} {
+ # Use pure lists to speed this up.
+ uplevel 1 [linsert $args 0 ::comm::comm send]
+ }
+ rename winfo tk_winfo
+ proc winfo {cmd args} {
+ if {![string match in* $cmd]} {
+ # Use pure lists to speed this up ...
+ return [uplevel 1 [linsert $args 0 tk_winfo $cmd]]
+ }
+ return [::comm::comm interps]
+ }
+ proc ::comm::comm_send {} {}
+}
+
+# ::comm::comm --
+#
+# See documentation for public methods of "comm".
+# This procedure is followed by the definition of
+# the public methods themselves.
+#
+# Arguments:
+# cmd Invoked method
+# args Arguments to method.
+#
+# Results:
+# As of the invoked method.
+
+proc ::comm::comm {cmd args} {
+ set method [info commands ::comm::comm_cmd_$cmd*]
+
+ if {[llength $method] == 1} {
+ set chan ::comm::comm; # passed to methods
+ return [uplevel 1 [linsert $args 0 $method $chan]]
+ } else {
+ foreach c [info commands ::comm::comm_cmd_*] {
+ # remove ::comm::comm_cmd_
+ lappend cmds [string range $c 17 end]
+ }
+ return -code error "unknown subcommand \"$cmd\":\
+ must be one of [join [lsort $cmds] {, }]"
+ }
+}
+
+proc ::comm::comm_cmd_connect {chan args} {
+ uplevel 1 [linsert $args 0 [namespace current]::commConnect $chan]
+}
+proc ::comm::comm_cmd_self {chan args} {
+ variable comm
+ return $comm($chan,port)
+}
+proc ::comm::comm_cmd_channels {chan args} {
+ variable comm
+ return $comm(chans)
+}
+proc ::comm::comm_cmd_configure {chan args} {
+ uplevel 1 [linsert $args 0 [namespace current]::commConfigure $chan 0]
+}
+proc ::comm::comm_cmd_ids {chan args} {
+ variable comm
+ set res $comm($chan,port)
+ foreach {i id} [array get comm $chan,fids,*] {lappend res $id}
+ return $res
+}
+interp alias {} ::comm::comm_cmd_interps {} ::comm::comm_cmd_ids
+proc ::comm::comm_cmd_remoteid {chan args} {
+ variable comm
+ if {[info exists comm($chan,remoteid)]} {
+ set comm($chan,remoteid)
+ } else {
+ return -code error "No remote commands processed yet"
+ }
+}
+proc ::comm::comm_cmd_debug {chan bool} {
+ variable comm
+ return [set comm(debug) [string is true -strict $bool]]
+}
+
+# ### ### ### ######### ######### #########
+## API: Setup async result generation for a remotely invoked command.
+
+# (future,fid,<fid>) -> list (future)
+# (current,async) -> bool (default 0)
+# (current,state) -> list (chan fid cmd ser)
+
+proc ::comm::comm_cmd_return_async {chan} {
+ variable comm
+
+ if {![info exists comm(current,async)]} {
+ return -code error "No remote commands processed yet"
+ }
+ if {$comm(current,async)} {
+ # Return the same future which were generated by the first
+ # call.
+ return $comm(current,state)
+ }
+
+ foreach {cmdchan cmdfid cmd ser} $comm(current,state) break
+
+ # Assert that the channel performing the request and the channel
+ # the current command came in are identical. Panic if not.
+
+ if {![string equal $chan $cmdchan]} {
+ return -code error "Internal error: Trying to activate\
+ async return for a command on a different channel"
+ }
+
+ # Establish the future for the command and return a handle for
+ # it. Remember the outstanding futures for a peer, so that we can
+ # cancel them if the peer is lost before the promise implicit in
+ # the future is redeemed.
+
+ set future [::comm::future %AUTO% $chan $cmdfid $cmd $ser]
+
+ lappend comm(future,fid,$cmdfid) $future
+ set comm(current,state) $future
+
+ # Mark the current command as using async result return. We do
+ # this last to ensure that all errors in this method are reported
+ # through the regular channels.
+
+ set comm(current,async) 1
+
+ return $future
+}
+
+# hook --
+#
+# Internal command. Implements 'comm hook'.
+#
+# Arguments:
+# hook hook to modify
+# script Script to add/remove to/from the hook
+#
+# Results:
+# None.
+#
+proc ::comm::comm_cmd_hook {chan hook {script +}} {
+ variable comm
+ if {![info exists comm($hook,hook)]} {
+ return -code error "Unknown hook invoked"
+ }
+ if {!$comm($hook,hook)} {
+ return -code error "Unimplemented hook invoked"
+ }
+ if {[string equal + $script]} {
+ if {[catch {set comm($chan,hook,$hook)} ret]} {
+ return
+ }
+ return $ret
+ }
+ if {[string match +* $script]} {
+ append comm($chan,hook,$hook) \n [string range $script 1 end]
+ } else {
+ set comm($chan,hook,$hook) $script
+ }
+ return
+}
+
+# abort --
+#
+# Close down all peer connections.
+# Implements the 'comm abort' method.
+#
+# Arguments:
+# None.
+#
+# Results:
+# None.
+
+proc ::comm::comm_cmd_abort {chan} {
+ variable comm
+
+ foreach pid [array names comm $chan,peers,*] {
+ commLostConn $chan $comm($pid) "Connection aborted by request"
+ }
+}
+
+# destroy --
+#
+# Destroy the channel invoking it.
+# Implements the 'comm destroy' method.
+#
+# Arguments:
+# None.
+#
+# Results:
+# None.
+#
+proc ::comm::comm_cmd_destroy {chan} {
+ variable comm
+ catch {close $comm($chan,socket)}
+ comm_cmd_abort $chan
+ if {$comm($chan,interp) != {}} {
+ interp delete $comm($chan,interp)
+ }
+ catch {unset comm($chan,port)}
+ catch {unset comm($chan,local)}
+ catch {unset comm($chan,silent)}
+ catch {unset comm($chan,interp)}
+ catch {unset comm($chan,events)}
+ catch {unset comm($chan,socket)}
+ catch {unset comm($chan,socketcmd)}
+ catch {unset comm($chan,remoteid)}
+ unset comm($chan,serial)
+ unset comm($chan,chan)
+ unset comm($chan,encoding)
+ unset comm($chan,listen)
+ # array unset would have been nicer, but is not available in
+ # 8.2/8.3
+ foreach pattern {hook,* interp,* vers,*} {
+ foreach k [array names comm $chan,$pattern] {unset comm($k)}
+ }
+ set pos [lsearch -exact $comm(chans) $chan]
+ set comm(chans) [lreplace $comm(chans) $pos $pos]
+ if {
+ ![string equal ::comm::comm $chan] &&
+ ![string equal [info proc $chan] ""]
+ } {
+ rename $chan {}
+ }
+ return
+}
+
+# shutdown --
+#
+# Close down a peer connection.
+# Implements the 'comm shutdown' method.
+#
+# Arguments:
+# id Reference to the remote interp
+#
+# Results:
+# None.
+#
+proc ::comm::comm_cmd_shutdown {chan id} {
+ variable comm
+
+ if {[info exists comm($chan,peers,$id)]} {
+ commLostConn $chan $comm($chan,peers,$id) \
+ "Connection shutdown by request"
+ }
+}
+
+# new --
+#
+# Create a new comm channel/instance.
+# Implements the 'comm new' method.
+#
+# Arguments:
+# ch Name of the new channel
+# args Configuration, in the form of -option value pairs.
+#
+# Results:
+# None.
+#
+proc ::comm::comm_cmd_new {chan ch args} {
+ variable comm
+
+ if {[lsearch -exact $comm(chans) $ch] >= 0} {
+ return -code error "Already existing channel: $ch"
+ }
+ if {([llength $args] % 2) != 0} {
+ return -code error "Must have an even number of config arguments"
+ }
+ # ensure that the new channel name is fully qualified
+ set ch ::[string trimleft $ch :]
+ if {[string equal ::comm::comm $ch]} {
+ # allow comm to be recreated after destroy
+ } elseif {[string equal $ch [info commands $ch]]} {
+ return -code error "Already existing command: $ch"
+ } else {
+ # Create the new channel with fully qualified proc name
+ proc $ch {cmd args} {
+ set method [info commands ::comm::comm_cmd_$cmd*]
+
+ if {[llength $method] == 1} {
+ # this should work right even if aliased
+ # it is passed to methods to identify itself
+ set chan [namespace origin [lindex [info level 0] 0]]
+ return [uplevel 1 [linsert $args 0 $method $chan]]
+ } else {
+ foreach c [info commands ::comm::comm_cmd_*] {
+ # remove ::comm::comm_cmd_
+ lappend cmds [string range $c 17 end]
+ }
+ return -code error "unknown subcommand \"$cmd\":\
+ must be one of [join [lsort $cmds] {, }]"
+ }
+ }
+ }
+ lappend comm(chans) $ch
+ set chan $ch
+ set comm($chan,serial) 0
+ set comm($chan,chan) $chan
+ set comm($chan,port) 0
+ set comm($chan,listen) 0
+ set comm($chan,socket) ""
+ set comm($chan,local) 1
+ set comm($chan,silent) $comm(defaultSilent)
+ set comm($chan,encoding) $comm(defaultEncoding)
+ set comm($chan,interp) {}
+ set comm($chan,events) {}
+ set comm($chan,socketcmd) ::socket
+
+ if {[llength $args] > 0} {
+ if {[catch [linsert $args 0 commConfigure $chan 1] err]} {
+ comm_cmd_destroy $chan
+ return -code error $err
+ }
+ }
+ return $chan
+}
+
+# send --
+#
+# Send command to a specified channel.
+# Implements the 'comm send' method.
+#
+# Arguments:
+# args see inside
+#
+# Results:
+# varies.
+#
+proc ::comm::comm_cmd_send {chan args} {
+ variable comm
+
+ set cmd send
+
+ # args = ?-async | -command command? id cmd ?arg arg ...?
+ set i 0
+ set opt [lindex $args $i]
+ if {[string equal -async $opt]} {
+ set cmd async
+ incr i
+ } elseif {[string equal -command $opt]} {
+ set cmd command
+ set callback [lindex $args [incr i]]
+ incr i
+ }
+ # args = id cmd ?arg arg ...?
+
+ set id [lindex $args $i]
+ incr i
+ set args [lrange $args $i end]
+
+ if {![info complete $args]} {
+ return -code error "Incomplete command"
+ }
+ if {![llength $args]} {
+ return -code error \
+ "wrong # args: should be \"send ?-async? id arg ?arg ...?\""
+ }
+ if {[catch {commConnect $chan $id} fid]} {
+ return -code error "Connect to remote failed: $fid"
+ }
+
+ set ser [incr comm($chan,serial)]
+ # This is unneeded - wraps from 2147483647 to -2147483648
+ ### if {$comm($chan,serial) == 0x7fffffff} {set comm($chan,serial) 0}
+
+ commDebug {puts stderr "<$chan> send <[list [list $cmd $ser $args]]>"}
+
+ # The double list assures that the command is a single list when read.
+ puts $fid [list [list $cmd $ser $args]]
+ flush $fid
+
+ commDebug {puts stderr "<$chan> sent"}
+
+ # wait for reply if so requested
+
+ if {[string equal command $cmd]} {
+ # In this case, don't wait on the command result. Set the callback
+ # in the return and that will be invoked by the result.
+ lappend comm($chan,pending,$id) [list $ser callback]
+ set comm($chan,return,$ser) $callback
+ return $ser
+ } elseif {[string equal send $cmd]} {
+ upvar 0 comm($chan,pending,$id) pending ;# shorter variable name
+
+ lappend pending $ser
+ set comm($chan,return,$ser) "" ;# we're waiting
+
+ commDebug {puts stderr "<$chan> --<<waiting $ser>>--"}
+ vwait ::comm::comm($chan,result,$ser)
+
+ # if connection was lost, pending is gone
+ if {[info exists pending]} {
+ set pos [lsearch -exact $pending $ser]
+ set pending [lreplace $pending $pos $pos]
+ }
+
+ commDebug {
+ puts stderr "<$chan> result\
+ <$comm($chan,return,$ser);$comm($chan,result,$ser)>"
+ }
+
+ array set return $comm($chan,return,$ser)
+ unset comm($chan,return,$ser)
+ set thisres $comm($chan,result,$ser)
+ unset comm($chan,result,$ser)
+ switch -- $return(-code) {
+ "" - 0 {return $thisres}
+ 1 {
+ return -code $return(-code) \
+ -errorinfo $return(-errorinfo) \
+ -errorcode $return(-errorcode) \
+ $thisres
+ }
+ default {return -code $return(-code) $thisres}
+ }
+ }
+}
+
+###############################################################################
+
+# ::comm::commDebug --
+#
+# Internal command. Conditionally executes debugging
+# statements. Currently this are only puts commands logging the
+# various interactions. These could be replaced with calls into
+# the 'log' module.
+#
+# Arguments:
+# arg Tcl script to execute.
+#
+# Results:
+# None.
+
+proc ::comm::commDebug {cmd} {
+ variable comm
+ if {$comm(debug)} {
+ uplevel 1 $cmd
+ }
+}
+
+# ::comm::commConfVars --
+#
+# Internal command. Used to declare configuration options.
+#
+# Arguments:
+# v Name of configuration option.
+# t Default value.
+#
+# Results:
+# None.
+
+proc ::comm::commConfVars {v t} {
+ variable comm
+ set comm($v,var) $t
+ set comm(vars) {}
+ foreach c [array names comm *,var] {
+ lappend comm(vars) [lindex [split $c ,] 0]
+ }
+ return
+}
+::comm::commConfVars port p
+::comm::commConfVars local b
+::comm::commConfVars listen b
+::comm::commConfVars socket ro
+::comm::commConfVars socketcmd socketcmd
+::comm::commConfVars chan ro
+::comm::commConfVars serial ro
+::comm::commConfVars encoding enc
+::comm::commConfVars silent b
+::comm::commConfVars interp interp
+::comm::commConfVars events ev
+
+# ::comm::commConfigure --
+#
+# Internal command. Implements 'comm configure'.
+#
+# Arguments:
+# force Boolean flag. If set the socket is reinitialized.
+# args New configuration, as -option value pairs.
+#
+# Results:
+# None.
+
+proc ::comm::commConfigure {chan {force 0} args} {
+ variable comm
+
+ # query
+ if {[llength $args] == 0} {
+ foreach v $comm(vars) {lappend res -$v $comm($chan,$v)}
+ return $res
+ } elseif {[llength $args] == 1} {
+ set arg [lindex $args 0]
+ set var [string range $arg 1 end]
+ if {![string match -* $arg] || ![info exists comm($var,var)]} {
+ return -code error "Unknown configuration option: $arg"
+ }
+ return $comm($chan,$var)
+ }
+
+ # set
+ set opt 0
+ foreach arg $args {
+ incr opt
+ if {[info exists skip]} {unset skip; continue}
+ set var [string range $arg 1 end]
+ if {![string match -* $arg] || ![info exists comm($var,var)]} {
+ return -code error "Unknown configuration option: $arg"
+ }
+ set optval [lindex $args $opt]
+ switch $comm($var,var) {
+ ev {
+ if {![string equal $optval ""]} {
+ set err 0
+ if {[catch {
+ foreach ev $optval {
+ if {[lsearch -exact {connecting connected incoming eval callback reply lost} $ev] < 0} {
+ set err 1
+ break
+ }
+ }
+ }]} {
+ set err 1
+ }
+ if {$err} {
+ return -code error \
+ "Non-event to configuration option: -$var"
+ }
+ }
+ # FRINK: nocheck
+ set $var $optval
+ set skip 1
+ }
+ interp {
+ if {
+ ![string equal $optval ""] &&
+ ![interp exists $optval]
+ } {
+ return -code error \
+ "Non-interpreter to configuration option: -$var"
+ }
+ # FRINK: nocheck
+ set $var $optval
+ set skip 1
+ }
+ b {
+ # FRINK: nocheck
+ set $var [string is true -strict $optval]
+ set skip 1
+ }
+ v {
+ # FRINK: nocheck
+ set $var $optval
+ set skip 1
+ }
+ p {
+ if {
+ ![string equal $optval ""] &&
+ ![string is integer $optval]
+ } {
+ return -code error \
+ "Non-port to configuration option: -$var"
+ }
+ # FRINK: nocheck
+ set $var $optval
+ set skip 1
+ }
+ i {
+ if {![string is integer $optval]} {
+ return -code error \
+ "Non-integer to configuration option: -$var"
+ }
+ # FRINK: nocheck
+ set $var $optval
+ set skip 1
+ }
+ enc {
+ # to configure encodings, we will need to extend the
+ # protocol to allow for handshaked encoding changes
+ return -code error "encoding not configurable"
+ if {[lsearch -exact [encoding names] $optval] == -1} {
+ return -code error \
+ "Unknown encoding to configuration option: -$var"
+ }
+ set $var $optval
+ set skip 1
+ }
+ ro {
+ return -code error "Readonly configuration option: -$var"
+ }
+ socketcmd {
+ if {$optval eq {}} {
+ return -code error \
+ "Non-command to configuration option: -$var"
+ }
+
+ set $var $optval
+ set skip 1
+ }
+ }
+ }
+ if {[info exists skip]} {
+ return -code error "Missing value for option: $arg"
+ }
+
+ foreach var {port listen local socketcmd} {
+ # FRINK: nocheck
+ if {[info exists $var] && [set $var] != $comm($chan,$var)} {
+ incr force
+ # FRINK: nocheck
+ set comm($chan,$var) [set $var]
+ }
+ }
+
+ foreach var {silent interp events} {
+ # FRINK: nocheck
+ if {[info exists $var] && ([set $var] != $comm($chan,$var))} {
+ # FRINK: nocheck
+ set comm($chan,$var) [set ip [set $var]]
+ if {[string equal $var "interp"] && ($ip != "")} {
+ # Interrogate the interp about its capabilities.
+ #
+ # Like: set, array set, uplevel present ?
+ # Or: The above, hidden ?
+ #
+ # This is needed to decide how to execute hook scripts
+ # and regular scripts in this interpreter.
+ set comm($chan,interp,set) [Capability $ip set]
+ set comm($chan,interp,aset) [Capability $ip array]
+ set comm($chan,interp,upl) [Capability $ip uplevel]
+ }
+ }
+ }
+
+ if {[info exists encoding] &&
+ ![string equal $encoding $comm($chan,encoding)]} {
+ # This should not be entered yet
+ set comm($chan,encoding) $encoding
+ fconfigure $comm($chan,socket) -encoding $encoding
+ foreach {i sock} [array get comm $chan,peers,*] {
+ fconfigure $sock -encoding $encoding
+ }
+ }
+
+ # do not re-init socket
+ if {!$force} {return ""}
+
+ # User is recycling object, possibly to change from local to !local
+ if {[info exists comm($chan,socket)]} {
+ comm_cmd_abort $chan
+ catch {close $comm($chan,socket)}
+ unset comm($chan,socket)
+ }
+
+ set comm($chan,socket) ""
+ if {!$comm($chan,listen)} {
+ set comm($chan,port) 0
+ return ""
+ }
+
+ if {[info exists port] && [string equal "" $comm($chan,port)]} {
+ set nport [incr comm(lastport)]
+ } else {
+ set userport 1
+ set nport $comm($chan,port)
+ }
+ while {1} {
+ set cmd [list $comm($chan,socketcmd) -server [list ::comm::commIncoming $chan]]
+ if {$comm($chan,local)} {
+ lappend cmd -myaddr $comm(localhost)
+ }
+ lappend cmd $nport
+ if {![catch $cmd ret]} {
+ break
+ }
+ if {[info exists userport] || ![string match "*already in use" $ret]} {
+ # don't eradicate the class
+ if {
+ ![string equal ::comm::comm $chan] &&
+ ![string equal [info proc $chan] ""]
+ } {
+ rename $chan {}
+ }
+ return -code error $ret
+ }
+ set nport [incr comm(lastport)]
+ }
+ set comm($chan,socket) $ret
+ fconfigure $ret -translation lf -encoding $comm($chan,encoding)
+
+ # If port was 0, system allocated it for us
+ set comm($chan,port) [lindex [fconfigure $ret -sockname] 2]
+ return ""
+}
+
+# ::comm::Capability --
+#
+# Internal command. Interogate an interp for
+# the commands needed to execute regular and
+# hook scripts.
+
+proc ::comm::Capability {interp cmd} {
+ if {[lsearch -exact [interp hidden $interp] $cmd] >= 0} {
+ # The command is present, although hidden.
+ return hidden
+ }
+
+ # The command is not a hidden command. Use info to determine if it
+ # is present as regular command. Note that the 'info' command
+ # itself might be hidden.
+
+ if {[catch {
+ set has [llength [interp eval $interp [list info commands $cmd]]]
+ }] && [catch {
+ set has [llength [interp invokehidden $interp info commands $cmd]]
+ }]} {
+ # Unable to interogate the interpreter in any way. Assume that
+ # the command is not present.
+ set has 0
+ }
+ return [expr {$has ? "ok" : "no"}]
+}
+
+# ::comm::commConnect --
+#
+# Internal command. Called to connect to a remote interp
+#
+# Arguments:
+# id Specification of the location of the remote interp.
+# A list containing either one or two elements.
+# One element = port, host is localhost.
+# Two elements = port and host, in this order.
+#
+# Results:
+# fid channel handle of the socket the connection goes through.
+
+proc ::comm::commConnect {chan id} {
+ variable comm
+
+ commDebug {puts stderr "<$chan> commConnect $id"}
+
+ # process connecting hook now
+ CommRunHook $chan connecting
+
+ if {[info exists comm($chan,peers,$id)]} {
+ return $comm($chan,peers,$id)
+ }
+ if {[lindex $id 0] == 0} {
+ return -code error "Remote comm is anonymous; cannot connect"
+ }
+
+ if {[llength $id] > 1} {
+ set host [lindex $id 1]
+ } else {
+ set host $comm(localhost)
+ }
+ set port [lindex $id 0]
+ set fid [$comm($chan,socketcmd) $host $port]
+
+ # process connected hook now
+ if {[catch {
+ CommRunHook $chan connected
+ } err]} {
+ global errorInfo
+ set ei $errorInfo
+ close $fid
+ error $err $ei
+ }
+
+ # commit new connection
+ commNewConn $chan $id $fid
+
+ # send offered protocols versions and id to identify ourselves to remote
+ puts $fid [list $comm(offerVers) $comm($chan,port)]
+ set comm($chan,vers,$id) $comm(defVers) ;# default proto vers
+ flush $fid
+ return $fid
+}
+
+# ::comm::commIncoming --
+#
+# Internal command. Called for an incoming new connection.
+# Handles connection setup and initialization.
+#
+# Arguments:
+# chan logical channel handling the connection.
+# fid channel handle of the socket running the connection.
+# addr ip address of the socket channel 'fid'
+# remport remote port for the socket channel 'fid'
+#
+# Results:
+# None.
+
+proc ::comm::commIncoming {chan fid addr remport} {
+ variable comm
+
+ commDebug {puts stderr "<$chan> commIncoming $fid $addr $remport"}
+
+ # process incoming hook now
+ if {[catch {
+ CommRunHook $chan incoming
+ } err]} {
+ global errorInfo
+ set ei $errorInfo
+ close $fid
+ error $err $ei
+ }
+
+ # Wait for offered version, without blocking the entire system.
+ # Bug 3066872. For a Tcl 8.6 implementation consider use of
+ # coroutines to hide the CSP and properly handle everything
+ # event based.
+
+ fconfigure $fid -blocking 0
+ fileevent $fid readable [list ::comm::commIncomingOffered $chan $fid $addr $remport]
+ return
+}
+
+proc ::comm::commIncomingOffered {chan fid addr remport} {
+ variable comm
+
+ # Check if we have a complete line.
+ if {[gets $fid protoline] < 0} {
+ #commDebug {puts stderr "commIncomingOffered: no data"}
+ if {[eof $fid]} {
+ commDebug {puts stderr "commIncomingOffered: eof on fid=$fid"}
+ catch {
+ close $fid
+ }
+ }
+ return
+ }
+
+ # Protocol version line has been received, disable event handling
+ # again.
+ fileevent $fid readable {}
+ fconfigure $fid -blocking 1
+
+ # a list of offered proto versions is the first word of first line
+ # remote id is the second word of first line
+ # rest of first line is ignored
+
+ set offeredvers [lindex $protoline 0]
+ set remid [lindex $protoline 1]
+
+ commDebug {puts stderr "<$chan> offered <$protoline>"}
+
+ # use the first supported version in the offered list
+ foreach v $offeredvers {
+ if {[info exists comm($v,vers)]} {
+ set vers $v
+ break
+ }
+ }
+ if {![info exists vers]} {
+ close $fid
+ if {[info exists comm($chan,silent)] &&
+ [string is true -strict $comm($chan,silent)]} then return
+ error "Unknown offered protocols \"$protoline\" from $addr/$remport"
+ }
+
+ # If the remote host addr isn't our local host addr,
+ # then add it to the remote id.
+ if {[string equal [lindex [fconfigure $fid -sockname] 0] $addr]} {
+ set id $remid
+ } else {
+ set id [list $remid $addr]
+ }
+
+ # Detect race condition of two comms connecting to each other
+ # simultaneously. It is OK when we are talking to ourselves.
+
+ if {[info exists comm($chan,peers,$id)] && $id != $comm($chan,port)} {
+
+ puts stderr "commIncoming race condition: $id"
+ puts stderr "peers=$comm($chan,peers,$id) port=$comm($chan,port)"
+
+ # To avoid the race, we really want to terminate one connection.
+ # However, both sides are committed to using it.
+ # commConnect needs to be synchronous and detect the close.
+ # close $fid
+ # return $comm($chan,peers,$id)
+ }
+
+ # Make a protocol response. Avoid any temptation to use {$vers > 2}
+ # - this forces forwards compatibility issues on protocol versions
+ # that haven't been invented yet. DON'T DO IT! Instead, test for
+ # each supported version explicitly. I.e., {$vers >2 && $vers < 5} is OK.
+
+ switch $vers {
+ 3 {
+ # Respond with the selected version number
+ puts $fid [list [list vers $vers]]
+ flush $fid
+ }
+ }
+
+ # commit new connection
+ commNewConn $chan $id $fid
+ set comm($chan,vers,$id) $vers
+}
+
+# ::comm::commNewConn --
+#
+# Internal command. Common new connection processing
+#
+# Arguments:
+# id Reference to the remote interp
+# fid channel handle of the socket running the connection.
+#
+# Results:
+# None.
+
+proc ::comm::commNewConn {chan id fid} {
+ variable comm
+
+ commDebug {puts stderr "<$chan> commNewConn $id $fid"}
+
+ # There can be a race condition two where comms connect to each other
+ # simultaneously. This code favors our outgoing connection.
+
+ if {[info exists comm($chan,peers,$id)]} {
+ # abort this connection, use the existing one
+ # close $fid
+ # return -code return $comm($chan,peers,$id)
+ } else {
+ set comm($chan,pending,$id) {}
+ set comm($chan,peers,$id) $fid
+ }
+ set comm($chan,fids,$fid) $id
+ fconfigure $fid -translation lf -encoding $comm($chan,encoding) -blocking 0
+ fileevent $fid readable [list ::comm::commCollect $chan $fid]
+}
+
+# ::comm::commLostConn --
+#
+# Internal command. Called to tidy up a lost connection,
+# including aborting ongoing sends. Each send should clean
+# themselves up in pending/result.
+#
+# Arguments:
+# fid Channel handle of the socket which got lost.
+# reason Message describing the reason of the loss.
+#
+# Results:
+# reason
+
+proc ::comm::commLostConn {chan fid reason} {
+ variable comm
+
+ commDebug {puts stderr "<$chan> commLostConn $fid $reason"}
+
+ catch {close $fid}
+
+ set id $comm($chan,fids,$fid)
+
+ # Invoke the callbacks of all commands which have such and are
+ # still waiting for a response from the lost peer. Use an
+ # appropriate error.
+
+ foreach s $comm($chan,pending,$id) {
+ if {[string equal "callback" [lindex $s end]]} {
+ set ser [lindex $s 0]
+ if {[info exists comm($chan,return,$ser)]} {
+ set args [list -id $id \
+ -serial $ser \
+ -chan $chan \
+ -code -1 \
+ -errorcode NONE \
+ -errorinfo "" \
+ -result $reason \
+ ]
+ if {[catch {uplevel \#0 $comm($chan,return,$ser) $args} err]} {
+ commBgerror $err
+ }
+ }
+ } else {
+ set comm($chan,return,$s) {-code error}
+ set comm($chan,result,$s) $reason
+ }
+ }
+ unset comm($chan,pending,$id)
+ unset comm($chan,fids,$fid)
+ catch {unset comm($chan,peers,$id)} ;# race condition
+ catch {unset comm($chan,buf,$fid)}
+
+ # Cancel all outstanding futures for requests which were made by
+ # the lost peer, if there are any. This does not destroy
+ # them. They will stay around until the long-running operations
+ # they belong too kill them.
+
+ CancelFutures $fid
+
+ # process lost hook now
+ catch {CommRunHook $chan lost}
+
+ return $reason
+}
+
+proc ::comm::commBgerror {err} {
+ # SF Tcllib Patch #526499
+ # (See http://sourceforge.net/tracker/?func=detail&aid=526499&group_id=12883&atid=312883
+ # for initial request and comments)
+ #
+ # Error in async call. Look for [bgerror] to report it. Same
+ # logic as in Tcl itself. Errors thrown by bgerror itself get
+ # reported to stderr.
+ if {[catch {bgerror $err} msg]} {
+ puts stderr "bgerror failed to handle background error."
+ puts stderr " Original error: $err"
+ puts stderr " Error in bgerror: $msg"
+ flush stderr
+ }
+}
+
+# CancelFutures: Mark futures associated with a comm channel as
+# expired, done when the connection to the peer has been lost. The
+# marked futures will not generate result anymore. They will also stay
+# around until destroyed by the script they belong to.
+
+proc ::comm::CancelFutures {fid} {
+ variable comm
+ if {![info exists comm(future,fid,$fid)]} return
+
+ commDebug {puts stderr "\tCanceling futures: [join $comm(future,fid,$fid) \
+ "\n\t : "]"}
+
+ foreach future $comm(future,fid,$fid) {
+ $future Cancel
+ }
+
+ unset comm(future,fid,$fid)
+ return
+}
+
+###############################################################################
+
+# ::comm::commCollect --
+#
+# Internal command. Called from the fileevent to read from fid
+# and append to the buffer. This continues until we get a whole
+# command, which we then invoke.
+#
+# Arguments:
+# chan logical channel collecting the data
+# fid channel handle of the socket we collect.
+#
+# Results:
+# None.
+
+proc ::comm::commCollect {chan fid} {
+ variable comm
+ upvar #0 comm($chan,buf,$fid) data
+
+ # Tcl8 may return an error on read after a close
+ if {[catch {read $fid} nbuf] || [eof $fid]} {
+ commDebug {puts stderr "<$chan> collect/lost eof $fid = [eof $fid]"}
+ commDebug {puts stderr "<$chan> collect/lost nbuf = <$nbuf>"}
+ commDebug {puts stderr "<$chan> collect/lost [fconfigure $fid]"}
+
+ fileevent $fid readable {} ;# be safe
+ commLostConn $chan $fid "target application died or connection lost"
+ return
+ }
+ append data $nbuf
+
+ commDebug {puts stderr "<$chan> collect <$data>"}
+
+ # If data contains at least one complete command, we will
+ # be able to take off the first element, which is a list holding
+ # the command. This is true even if data isn't a well-formed
+ # list overall, with unmatched open braces. This works because
+ # each command in the protocol ends with a newline, thus allowing
+ # lindex and lreplace to work.
+ #
+ # This isn't true with Tcl8.0, which will return an error until
+ # the whole buffer is a valid list. This is probably OK, although
+ # it could potentially cause a deadlock.
+
+ # [AK] Actually no. This breaks down if the sender shoves so much
+ # data at us so fast that the receiver runs into out of memory
+ # before the list is fully well-formed and thus able to be
+ # processed.
+
+ while {![catch {
+ set cmdrange [Word0 data]
+ # word0 is essentially the pre-8.0 'lindex <list> 0', getting
+ # the first word of a list, even if the remainder is not fully
+ # well-formed. Slight API change, we get the char indices the
+ # word is between, and a relative index to the remainder of
+ # the list.
+ }]} {
+ # Unpack the indices, then extract the word.
+ foreach {s e step} $cmdrange break
+ set cmd [string range $data $s $e]
+ commDebug {puts stderr "<$chan> cmd <$data>"}
+ if {[string equal "" $cmd]} break
+ if {[info complete $cmd]} {
+ # The word is a command, step to the remainder of the
+ # list, and delete the word we have processed.
+ incr e $step
+ set data [string range $data $e end]
+ after idle \
+ [list ::comm::commExec $chan $fid $comm($chan,fids,$fid) $cmd]
+ }
+ }
+}
+
+proc ::comm::Word0 {dv} {
+ upvar 1 $dv data
+
+ # data
+ #
+ # The string we expect to be either a full well-formed list, or a
+ # well-formed list until the end of the first word in the list,
+ # with non-wellformed data following after, i.e. an incomplete
+ # list with a complete first word.
+
+ if {[regexp -indices "^\\s*(\{)" $data -> bracerange]} {
+ # The word is brace-quoted, starting at index 'lindex
+ # bracerange 0'. We now have to find the closing brace,
+ # counting inner braces, ignoring quoted braces. We fail if
+ # there is no proper closing brace.
+
+ foreach {s e} $bracerange break
+ incr s ; # index of the first char after the brace.
+ incr e ; # same. but this is our running index.
+
+ set level 1
+ set max [string length $data]
+
+ while {$level} {
+ # We are looking for the first regular or backslash-quoted
+ # opening or closing brace in the string. If none is found
+ # then the word is not complete, and we abort our search.
+
+ # Bug 2972571: To avoid the bogus detection of
+ # backslash-quoted braces we look for double-backslashes
+ # as well and skip them. Without this a string like '{puts
+ # \\}' will incorrectly find a \} at the end, missing the
+ # end of the word.
+
+ if {![regexp -indices -start $e {((\\\\)|([{}])|(\\[{}]))} $data -> any dbs regular quoted]} {
+ # ^^ ^ ^
+ # |\\ regular \quoted
+ # any
+ return -code error "no complete word found/1"
+ }
+
+ foreach {ds de} $dbs break
+ foreach {qs qe} $quoted break
+ foreach {rs re} $regular break
+
+ if {$ds >= 0} {
+ # Skip double-backslashes ...
+ set e $de
+ incr e
+ continue
+ } elseif {$qs >= 0} {
+ # Skip quoted braces ...
+ set e $qe
+ incr e
+ continue
+ } elseif {$rs >= 0} {
+ # Step one nesting level in or out.
+ if {[string index $data $rs] eq "\{"} {
+ incr level
+ } else {
+ incr level -1
+ }
+ set e $re
+ incr e
+ #puts @$e
+ continue
+ } else {
+ return -code error "internal error"
+ }
+ }
+
+ incr e -2 ; # index of character just before the brace.
+ return [list $s $e 2]
+
+ } elseif {[regexp -indices {^\s*(\S+)\s} $data -> wordrange]} {
+ # The word is a simple literal which ends at the next
+ # whitespace character. Note that there has to be a whitespace
+ # for us to recognize a word, for while there is no whitespace
+ # behind it in the buffer the word itself may be incomplete.
+
+ return [linsert $wordrange end 1]
+ }
+
+ return -code error "no complete word found/2"
+}
+
+# ::comm::commExec --
+#
+# Internal command. Receives and executes a remote command,
+# returning the result and/or error. Unknown protocol commands
+# are silently discarded
+#
+# Arguments:
+# chan logical channel collecting the data
+# fid channel handle of the socket we collect.
+# remoteid id of the other side.
+# buf buffer containing the command to execute.
+#
+# Results:
+# None.
+
+proc ::comm::commExec {chan fid remoteid buf} {
+ variable comm
+
+ # buffer should contain:
+ # send # {cmd} execute cmd and send reply with serial #
+ # async # {cmd} execute cmd but send no reply
+ # reply # {cmd} execute cmd as reply to serial #
+
+ # these variables are documented in the hook interface
+ set cmd [lindex $buf 0]
+ set ser [lindex $buf 1]
+ set buf [lrange $buf 2 end]
+ set buffer [lindex $buf 0]
+
+ # Save remoteid for "comm remoteid". This will only be valid
+ # if retrieved before any additional events occur on this channel.
+ # N.B. we could have already lost the connection to remote, making
+ # this id be purely informational!
+ set comm($chan,remoteid) [set id $remoteid]
+
+ # Save state for possible async result generation
+ AsyncPrepare $chan $fid $cmd $ser
+
+ commDebug {puts stderr "<$chan> exec <$cmd,$ser,$buf>"}
+
+ switch -- $cmd {
+ send - async - command {}
+ callback {
+ if {![info exists comm($chan,return,$ser)]} {
+ commDebug {puts stderr "<$chan> No one waiting for serial \"$ser\""}
+ return
+ }
+
+ # Decompose reply command to assure it only uses "return"
+ # with no side effects.
+
+ array set return {-code "" -errorinfo "" -errorcode ""}
+ set ret [lindex $buffer end]
+ set len [llength $buffer]
+ incr len -2
+ foreach {sw val} [lrange $buffer 1 $len] {
+ if {![info exists return($sw)]} continue
+ set return($sw) $val
+ }
+
+ catch {CommRunHook $chan callback}
+
+ # this wakes up the sender
+ commDebug {puts stderr "<$chan> --<<wakeup $ser>>--"}
+
+ # the return holds the callback command
+ # string map the optional %-subs
+ set args [list -id $id \
+ -serial $ser \
+ -chan $chan \
+ -code $return(-code) \
+ -errorcode $return(-errorcode) \
+ -errorinfo $return(-errorinfo) \
+ -result $ret \
+ ]
+ set code [catch {uplevel \#0 $comm($chan,return,$ser) $args} err]
+ catch {unset comm($chan,return,$ser)}
+
+ # remove pending serial
+ upvar 0 comm($chan,pending,$id) pending
+ if {[info exists pending]} {
+ set pos [lsearch -exact $pending [list $ser callback]]
+ if {$pos != -1} {
+ set pending [lreplace $pending $pos $pos]
+ }
+ }
+ if {$code} {
+ commBgerror $err
+ }
+ return
+ }
+ reply {
+ if {![info exists comm($chan,return,$ser)]} {
+ commDebug {puts stderr "<$chan> No one waiting for serial \"$ser\""}
+ return
+ }
+
+ # Decompose reply command to assure it only uses "return"
+ # with no side effects.
+
+ array set return {-code "" -errorinfo "" -errorcode ""}
+ set ret [lindex $buffer end]
+ set len [llength $buffer]
+ incr len -2
+ foreach {sw val} [lrange $buffer 1 $len] {
+ if {![info exists return($sw)]} continue
+ set return($sw) $val
+ }
+
+ catch {CommRunHook $chan reply}
+
+ # this wakes up the sender
+ commDebug {puts stderr "<$chan> --<<wakeup $ser>>--"}
+ set comm($chan,result,$ser) $ret
+ set comm($chan,return,$ser) [array get return]
+ return
+ }
+ vers {
+ set ::comm::comm($chan,vers,$id) $ser
+ return
+ }
+ default {
+ commDebug {puts stderr "<$chan> unknown command; discard \"$cmd\""}
+ return
+ }
+ }
+
+ # process eval hook now
+ set done 0
+ set err 0
+ if {[info exists comm($chan,hook,eval)]} {
+ set err [catch {CommRunHook $chan eval} ret]
+ commDebug {puts stderr "<$chan> eval hook res <$err,$ret>"}
+ switch $err {
+ 1 {
+ # error
+ set done 1
+ }
+ 2 - 3 {
+ # return / break
+ set err 0
+ set done 1
+ }
+ }
+ }
+
+ commDebug {puts stderr "<$chan> hook(eval) done=$done, err=$err"}
+
+ # exec command
+ if {!$done} {
+ commDebug {puts stderr "<$chan> exec ($buffer)"}
+
+ # Sadly, the uplevel needs to be in the catch to access the local
+ # variables buffer and ret. These cannot simply be global because
+ # commExec is reentrant (i.e., they could be linked to an allocated
+ # serial number).
+
+ if {$comm($chan,interp) == {}} {
+ # Main interpreter
+ set thecmd [concat [list uplevel \#0] $buffer]
+ set err [catch $thecmd ret]
+ } else {
+ # Redirect execution into the configured slave
+ # interpreter. The exact command used depends on the
+ # capabilities of the interpreter. A best effort is made
+ # to execute the script in the global namespace.
+ set interp $comm($chan,interp)
+
+ if {$comm($chan,interp,upl) == "ok"} {
+ set thecmd [concat [list uplevel \#0] $buffer]
+ set err [catch {interp eval $interp $thecmd} ret]
+ } elseif {$comm($chan,interp,aset) == "hidden"} {
+ set thecmd [linsert $buffer 0 interp invokehidden $interp uplevel \#0]
+ set err [catch $thecmd ret]
+ } else {
+ set thecmd [concat [list interp eval $interp] $buffer]
+ set err [catch $thecmd ret]
+ }
+ }
+ }
+
+ # Check and handle possible async result generation.
+ if {[AsyncCheck]} return
+
+ commSendReply $chan $fid $cmd $ser $err $ret
+ return
+}
+
+# ::comm::commSendReply --
+#
+# Internal command. Executed to construct and send the reply
+# for a command.
+#
+# Arguments:
+# fid channel handle of the socket we are replying to.
+# cmd The type of request (send, command) we are replying to.
+# ser Serial number of the request the reply is for.
+# err result code to place into the reply.
+# ret result value to place into the reply.
+#
+# Results:
+# None.
+
+proc ::comm::commSendReply {chan fid cmd ser err ret} {
+ variable comm
+
+ commDebug {puts stderr "<$chan> res <$err,$ret> /$cmd"}
+
+ # The double list assures that the command is a single list when read.
+ if {[string equal send $cmd] || [string equal command $cmd]} {
+ # The catch here is just in case we lose the target. Consider:
+ # comm send $other comm send [comm self] exit
+ catch {
+ set return [list return -code $err]
+ # send error or result
+ if {$err == 1} {
+ global errorInfo errorCode
+ lappend return -errorinfo $errorInfo -errorcode $errorCode
+ }
+ lappend return $ret
+ if {[string equal send $cmd]} {
+ set reply reply
+ } else {
+ set reply callback
+ }
+ puts $fid [list [list $reply $ser $return]]
+ flush $fid
+ }
+ commDebug {puts stderr "<$chan> reply sent"}
+ }
+
+ if {$err == 1} {
+ commBgerror $ret
+ }
+ commDebug {puts stderr "<$chan> exec complete"}
+ return
+}
+
+proc ::comm::CommRunHook {chan event} {
+ variable comm
+
+ # The documentation promises the hook scripts to have access to a
+ # number of internal variables. For a regular hook we simply
+ # execute it in the calling level to fulfill this. When the hook
+ # is redirected into an interpreter however we do a best-effort
+ # copying of the variable values into the interpreter. Best-effort
+ # because the 'set' command may not be available in the
+ # interpreter, not even hidden.
+
+ if {![info exists comm($chan,hook,$event)]} return
+ set cmd $comm($chan,hook,$event)
+ set interp $comm($chan,interp)
+ commDebug {puts stderr "<$chan> hook($event) run <$cmd>"}
+
+ if {
+ ($interp != {}) &&
+ ([lsearch -exact $comm($chan,events) $event] >= 0)
+ } {
+ # Best-effort to copy the context into the interpreter for
+ # access by the hook script.
+ set vars {
+ addr buffer chan cmd fid host
+ id port reason remport ret var
+ }
+
+ if {$comm($chan,interp,set) == "ok"} {
+ foreach v $vars {
+ upvar 1 $v V
+ if {![info exists V]} continue
+ interp eval $interp [list set $v $V]
+ }
+ } elseif {$comm($chan,interp,set) == "hidden"} {
+ foreach v $vars {
+ upvar 1 $v V
+ if {![info exists V]} continue
+ interp invokehidden $interp set $v $V
+ }
+ }
+ upvar 1 return AV
+ if {[info exists AV]} {
+ if {$comm($chan,interp,aset) == "ok"} {
+ interp eval $interp [list array set return [array get AV]]
+ } elseif {$comm($chan,interp,aset) == "hidden"} {
+ interp invokehidden $interp array set return [array get AV]
+ }
+ }
+
+ commDebug {puts stderr "<$chan> /interp $interp"}
+ set code [catch {interp eval $interp $cmd} res]
+ } else {
+ commDebug {puts stderr "<$chan> /main"}
+ set code [catch {uplevel 1 $cmd} res]
+ }
+
+ # Perform the return code propagation promised
+ # to the hook scripts.
+ switch -exact -- $code {
+ 0 {}
+ 1 {
+ return -errorinfo $::errorInfo -errorcode $::errorCode -code error $res
+ }
+ 3 {return}
+ 4 {}
+ default {return -code $code $res}
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Hooks to link async return and future processing into the regular
+## system.
+
+# AsyncPrepare, AsyncCheck: Initialize state information for async
+# return upon start of a remote invokation, and checking the state for
+# async return.
+
+proc ::comm::AsyncPrepare {chan fid cmd ser} {
+ variable comm
+ set comm(current,async) 0
+ set comm(current,state) [list $chan $fid $cmd $ser]
+ return
+}
+
+proc ::comm::AsyncCheck {} {
+ # Check if the executed command notified us of an async return. If
+ # not we let the regular return processing handle the end of the
+ # script. Otherwise we stop the caller from proceeding, preventing
+ # a regular return.
+
+ variable comm
+ if {!$comm(current,async)} {return 0}
+ return 1
+}
+
+# FutureDone: Action taken by an uncanceled future to deliver the
+# generated result to the proper invoker. This also removes the future
+# from the list of pending futures for the comm channel.
+
+proc comm::FutureDone {future chan fid cmd sid rcode rvalue} {
+ variable comm
+ commSendReply $chan $fid $cmd $sid $rcode $rvalue
+
+ set pos [lsearch -exact $comm(future,fid,$fid) $future]
+ set comm(future,fid,$fid) [lreplace $comm(future,fid,$fid) $pos $pos]
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Hooks to save command state across nested eventloops a remotely
+## invoked command may run before finally activating async result
+## generation.
+
+# DANGER !! We have to refer to comm internals using fully-qualified
+# names because the wrappers will execute in the global namespace
+# after their installation.
+
+proc ::comm::Vwait {varname} {
+ variable ::comm::comm
+
+ set hasstate [info exists comm(current,async)]
+ set hasremote 0
+ if {$hasstate} {
+ set chan [lindex $comm(current,state) 0]
+ set async $comm(current,async)
+ set state $comm(current,state)
+ set hasremote [info exists comm($chan,remoteid)]
+ if {$hasremote} {
+ set remoteid $comm($chan,remoteid)
+ }
+ }
+
+ set code [catch {uplevel 1 [list ::comm::VwaitOrig $varname]} res]
+
+ if {$hasstate} {
+ set comm(current,async) $async
+ set comm(current,state) $state
+ }
+ if {$hasremote} {
+ set comm($chan,remoteid) $remoteid
+ }
+
+ return -code $code $res
+}
+
+proc ::comm::Update {args} {
+ variable ::comm::comm
+
+ set hasstate [info exists comm(current,async)]
+ set hasremote 0
+ if {$hasstate} {
+ set chan [lindex $comm(current,state) 0]
+ set async $comm(current,async)
+ set state $comm(current,state)
+
+ set hasremote [info exists comm($chan,remoteid)]
+ if {$hasremote} {
+ set remoteid $comm($chan,remoteid)
+ }
+ }
+
+ set code [catch {uplevel 1 [linsert $args 0 ::comm::UpdateOrig]} res]
+
+ if {$hasstate} {
+ set comm(current,async) $async
+ set comm(current,state) $state
+ }
+ if {$hasremote} {
+ set comm($chan,remoteid) $remoteid
+ }
+
+ return -code $code $res
+}
+
+# Install the wrappers.
+
+proc ::comm::InitWrappers {} {
+ rename ::vwait ::comm::VwaitOrig
+ rename ::comm::Vwait ::vwait
+
+ rename ::update ::comm::UpdateOrig
+ rename ::comm::Update ::update
+
+ proc ::comm::InitWrappers {} {}
+ return
+}
+
+# ### ### ### ######### ######### #########
+## API: Future objects.
+
+snit::type comm::future {
+ option -command -default {}
+
+ constructor {chan fid cmd ser} {
+ set xfid $fid
+ set xcmd $cmd
+ set xser $ser
+ set xchan $chan
+ return
+ }
+
+ destructor {
+ if {!$canceled} {
+ return -code error \
+ "Illegal attempt to destroy unresolved future \"$self\""
+ }
+ }
+
+ method return {args} {
+ # Syntax: | 0
+ # : -code x | 2
+ # : -code x val | 3
+ # : val | 4
+ # Allowing multiple -code settings, last one is taken.
+
+ set rcode 0
+ set rvalue {}
+
+ while {[lindex $args 0] == "-code"} {
+ set rcode [lindex $args 1]
+ set args [lrange $args 2 end]
+ }
+ if {[llength $args] > 1} {
+ return -code error "wrong\#args, expected \"?-code errcode? ?result?\""
+ }
+ if {[llength $args] == 1} {
+ set rvalue [lindex $args 0]
+ }
+
+ if {!$canceled} {
+ comm::FutureDone $self $xchan $xfid $xcmd $xser $rcode $rvalue
+ set canceled 1
+ }
+ # assert: canceled == 1
+ $self destroy
+ return
+ }
+
+ variable xfid {}
+ variable xcmd {}
+ variable xser {}
+ variable xchan {}
+ variable canceled 0
+
+ # Internal method for use by comm channels. Marks the future as
+ # expired, no peer to return a result back to.
+
+ method Cancel {} {
+ set canceled 1
+ if {![llength $options(-command)]} {return}
+ uplevel #0 [linsert $options(-command) end $self]
+ return
+ }
+}
+
+# ### ### ### ######### ######### #########
+## Setup
+::comm::InitWrappers
+
+###############################################################################
+#
+# Finish creating "comm" using the default port for this interp.
+#
+
+if {![info exists ::comm::comm(comm,port)]} {
+ if {[string equal macintosh $tcl_platform(platform)]} {
+ ::comm::comm new ::comm::comm -port 0 -local 0 -listen 1
+ set ::comm::comm(localhost) \
+ [lindex [fconfigure $::comm::comm(::comm::comm,socket) -sockname] 0]
+ ::comm::comm config -local 1
+ } else {
+ ::comm::comm new ::comm::comm -port 0 -local 1 -listen 1
+ }
+}
+
+#eof
+package provide comm 4.6.3.1
diff --git a/tcllib/modules/comm/comm.test b/tcllib/modules/comm/comm.test
new file mode 100644
index 0000000..2289446
--- /dev/null
+++ b/tcllib/modules/comm/comm.test
@@ -0,0 +1,318 @@
+# -*- tcl -*-
+# Tests for the comm module.
+#
+# Sourcing this file into Tcl runs the tests and generates output for errors.
+# No output means no errors were found.
+#
+# Copyright (c) 2001 by ActiveState Tool Corp.
+# All rights reserved.
+#
+# RCS: @(#) $Id: comm.test,v 1.14 2010/09/15 19:48:33 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.3 ; # snit
+testsNeedTcltest 1.0
+
+tcltest::testConstraint hastls [expr {![catch {package require tls}]}]
+
+support {
+ # Using snit1 here, whatever the version of Tcl
+ use snit/snit.tcl snit
+}
+testing {
+ useLocal comm.tcl comm
+}
+
+# ------------------------------------------------------------------------
+# First order of things is to spawn a separate tclsh into the background
+# and have it execute comm too, with some general code to respond to our
+# requests
+
+useLocalFile comm.slaveboot
+
+# ------------------------------------------------------------------------
+
+test comm-1.0 {set remote variable} {
+ ::comm::comm send [slave] {set foo b}
+} {b}
+
+test comm-1.1 {set remote variable, async} {
+ ::comm::comm send -async [slave] {set fox a}
+} {}
+
+test comm-1.2 {get remote variables} {
+ ::comm::comm send [slave] {list $foo $fox}
+} {b a}
+
+# ------------------------------------------------------------------------
+
+set hack [interp create]
+
+test comm-2.0 {-interp configuration} {
+ ::comm::comm configure -interp $hack
+} {}
+
+test comm-2.1 {-interp configuration} {
+ ::comm::comm configure -interp
+} $hack
+
+test comm-2.2 {-interp configuration} {
+ res!
+ res+ [::comm::comm configure -interp $hack] [::comm::comm configure -interp]
+ res+ [::comm::comm configure -interp {}] [::comm::comm configure -interp]
+ res?
+} [list [list {} $hack] {{} {}}]
+
+test comm-2.3 {-interp configuration} {
+ catch {::comm::comm configure -interp bad} msg
+ set msg
+} {Non-interpreter to configuration option: -interp}
+
+test comm-2.4 {-interp configuration, destruction} {
+ res!
+ res+ [interp exists $hack]
+ res+ [info commands FOO]
+ comm::comm new FOO -interp $hack
+ FOO destroy
+ res+ [interp exists $hack]
+ res+ [info commands FOO]
+ res?
+} {1 {{}} 0 {{}}}
+
+set hack [interp create]
+set beta [interp create]
+
+test comm-2.5 {-interp configuration, destruction} {
+ res!
+ res+ [interp exists $hack]
+ res+ [interp exists $beta]
+ res+ [info commands FOO]
+ comm::comm new FOO -interp $hack
+ FOO configure -interp $beta
+ FOO destroy
+ res+ [interp exists $hack]
+ res+ [interp exists $beta]
+ res+ [info commands FOO]
+ res?
+} {1 1 {{}} 1 0 {{}}}
+
+test comm-2.6 {-interp use for received scripts} {
+ set FOO [::comm::comm send [slave] {
+ set hack [interp create]
+ interp eval $hack {set fox 0}
+ comm::comm new FOO -interp $hack -listen 1
+ FOO self
+ }] ; # {}
+
+ comm::comm send $FOO {set fox 1}
+ set res [comm::comm send [slave] {
+ interp eval $hack {set fox}
+ }] ; # {}
+ comm::comm send [slave] {FOO destroy}
+ set res
+} 1
+
+test comm-2.7 {-interp use for received scripts} {
+ set FOO [::comm::comm send [slave] {
+ set hack [interp create]
+ interp eval $hack {set fox 0}
+ comm::comm new FOO -interp $hack -listen 1
+ FOO self
+ }] ; # {}
+
+ comm::comm send $FOO set fox 2
+ set res [comm::comm send [slave] {
+ interp eval $hack {set fox}
+ }] ; # {}
+ comm::comm send [slave] {FOO destroy}
+ set res
+} 2
+
+# ------------------------------------------------------------------------
+
+test comm-3.0 {-events configuration} {
+ ::comm::comm configure -events eval
+} {}
+
+test comm-3.1 {-events configuration} {
+ ::comm::comm configure -events
+} eval
+
+test comm-3.2 {-events configuration} {
+ res!
+ res+ [::comm::comm configure -events eval] [::comm::comm configure -events]
+ res+ [::comm::comm configure -events {}] [::comm::comm configure -events]
+ res?
+} {{{} eval} {{} {}}}
+
+test comm-3.3 {-events configuration} {
+ catch {::comm::comm configure -events bad} msg
+ set msg
+} {Non-event to configuration option: -events}
+
+
+test comm-3.4 {-interp use for -events scripts, eval} {
+ set FOO [::comm::comm send [slave] {
+ set hack [interp create]
+ interp eval $hack {set fox 0 ; set wolf 0}
+ comm::comm new FOO -interp $hack -listen 1 -events eval
+ FOO hook eval {set wolf 2}
+ FOO self
+ }] ; # {}
+
+ comm::comm send $FOO {set fox 1}
+ set res [comm::comm send [slave] {
+ interp eval $hack {set wolf}
+ }] ; # {}
+ comm::comm send [slave] {FOO destroy}
+ set res
+} 2
+
+# ------------------------------------------------------------------------
+
+test comm-4.0 {async generation of result on remote side} {
+ ::comm::comm send [slave] {
+ proc future {} {
+ set f [comm::comm return_async]
+ after 3000 [list $f return "delayed $f"]
+ return ignored
+ }
+ }
+ ::comm::comm send [slave] {future}
+} {delayed ::comm::future1}
+
+test comm-4.1 {async reception of a result via callback} {
+ set res {}
+ proc foo {args} {
+ array set tmp $args
+ unset tmp(-id)
+ unset tmp(-serial)
+ global res ; lappend res [dictsort [array get tmp]]
+ }
+ ::comm::comm send -command foo [slave] {list $foo $fox}
+ vwait res
+ rename foo {}
+ set res
+} {{-chan ::comm::comm -code 0 -errorcode {} -errorinfo {} -result {b a}}}
+
+test comm-4.2 {async generation/reception of results in parallel} {
+
+ # Setup long running operations with async result generation.
+ ::comm::comm send [slave] {
+ proc future {n x} {
+ set f [comm::comm return_async]
+ after $n [list $f return "delayed $x"]
+ return ignored
+ }
+ }
+
+ # Setup async receiver callback.
+ proc receive {args} {
+ global res tick tock
+ array set tmp $args
+ unset tmp(-id)
+ unset tmp(-serial)
+ unset tmp(-chan)
+ unset tmp(-code)
+ unset tmp(-errorcode)
+ unset tmp(-errorinfo)
+ lappend res [dictsort [array get tmp]]
+ incr tock -1
+ if {!$tock} {set tick .}
+ return
+ }
+
+ # Execute two requests, the second of which is returns before the first.
+ # The main point is that the server does process it due to first doing
+ # an async return.
+
+ set tick .
+ set tock 2
+ set res {}
+
+ ::comm::comm send -command receive [slave] {future 5000 A}
+ ::comm::comm send -command receive [slave] {future 2500 B}
+ vwait tick
+ rename receive {}
+ set res
+ # B returned before A, A was sent before B
+} {{-result {delayed B}} {-result {delayed A}}}
+
+
+test comm-4.3 {bug 2972571, handling of \\ by Word0} {
+ ::comm::comm send [slave] {
+ proc foo {args} {
+ return nothing
+ }
+ }
+ ::comm::comm send [slave] {foo \\}
+} {nothing}
+
+
+# ------------------------------------------------------------------------
+
+test comm-5.0 {-port already in use} {
+ # First start a server on port 12345
+ set port 12345
+ catch {set shdl [socket -server foo $port]}
+ catch {::comm::comm new bar -port $port -listen 1 -local 0} msg
+ catch {close $shdl}
+ unset -nocomplain shdl port
+ set msg
+} {couldn't open socket: address already in use}
+
+# ------------------------------------------------------------------------
+
+test comm-6.0 {secured communication via tls package} hastls {
+ # Setup secured channel in main process.
+ tls::init \
+ -keyfile [tcllibPath devtools/receiver.key] \
+ -certfile [tcllibPath devtools/receiver.crt] \
+ -cafile [tcllibPath devtools/ca.crt] \
+ -ssl2 1 \
+ -ssl3 1 \
+ -tls1 0 \
+ -require 1
+ comm::comm new BAR -socketcmd tls::socket -listen 1
+
+ # Setup secured channel in slave process
+ ::comm::comm send [slave] {
+ package require tls
+ set fox dog
+ }
+ ::comm::comm send [slave] \
+ [list \
+ tls::init \
+ -keyfile [tcllibPath devtools/transmitter.key] \
+ -certfile [tcllibPath devtools/transmitter.crt] \
+ -cafile [tcllibPath devtools/ca.crt] \
+ -ssl2 1 \
+ -ssl3 1 \
+ -tls1 0 \
+ -require 1]
+ set FOO [::comm::comm send [slave] {
+ comm::comm new FOO -socketcmd tls::socket -listen 1
+ FOO self
+ }] ; # {}
+
+ # Run command interaction over the secured channel
+ set res [BAR send $FOO {set fox}]
+
+ # Cleanup, remove secured endpoints
+ comm::comm send [slave] {FOO destroy}
+ BAR destroy
+
+ # Return result of the secured command
+ set res
+} dog
+
+# ------------------------------------------------------------------------
+
+slavestop
+testsuiteCleanup
+return
diff --git a/tcllib/modules/comm/comm_wire.man b/tcllib/modules/comm/comm_wire.man
new file mode 100644
index 0000000..e0419f1
--- /dev/null
+++ b/tcllib/modules/comm/comm_wire.man
@@ -0,0 +1,284 @@
+[manpage_begin comm_wire n 3]
+[see_also comm]
+[keywords comm]
+[keywords communication]
+[keywords ipc]
+[keywords message]
+[keywords {remote communication}]
+[keywords {remote execution}]
+[keywords rpc]
+[keywords socket]
+[copyright {2005 Docs. Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Remote communication}]
+[titledesc {The comm wire protocol}]
+[category {Programming tools}]
+[require comm]
+[description]
+
+[para]
+
+The [package comm] command provides an inter-interpreter remote
+execution facility much like Tk's [cmd send(n)], except that it uses
+sockets rather than the X server for the communication path. As a
+result, [package comm] works with multiple interpreters, works on
+Windows and Macintosh systems, and provides control over the remote
+execution path.
+
+[para]
+
+This document contains a specification of the various versions of the
+wire protocol used by comm internally for the communication between
+its endpoints. It has no relevance to users of [package comm], only to
+developers who wish to modify the package, write a compatible facility
+in a different language, or some other facility based on the same
+protocol.
+
+[comment {
+ An example of some other facility could be a router layer which is
+ able to get messages for many different endpoints and then routes
+ them to the correct one. Why is this interesting ? Because it
+ allows mesh-routing, i.e. an application fires a command to some
+ other endpoint without having to worry if there is direct
+ connection to this endpoint or not. A secure tunnel then neatly
+ fits into this. Its endpoints are routing agents which take
+ arbitrarily commands, route them through the tunnel and then
+ dispatch them to the correct endpoint on the other side.
+
+ Note: A special case would be to have such a router facility built
+ into the core comm package, making each endpoint a router as
+ well. Like with the ability to listen to for non-local connection
+ this is something the user should be able to disable.
+}]
+
+[comment {
+ Motivation for documenting the protocol
+ ---------------------------------------
+
+ While the comm package allows the transport and execution of arbitrary
+ Tcl scripts a particular application can use the hooks to restrict the
+ scripts to single commands, and the legal commands to a specific set
+ as well.
+
+ If this is done (*) comm becomes more of a transport layer for a
+ regular RPC, and the data transported over the wire is less of a Tcl
+ script and more of a declaration of which remote procedure is wanted,
+ plus arguments.
+
+ At this point it begins to make sense to have implementations in other
+ scripting languages. Because then it becomes irrelevant in what
+ language the server is implemented. The comm protocol becomes a
+ portable RPC protocol, which can also be used for transport Tcl
+ scripts when both sides are Tcl and allowing this.
+
+ (*) And IMHO it should be done 90% of the time, just to get proper
+ security. Note that just using a safe interp is not quite enough, as
+ it still allows arbitrary scripts. The interp has to contains aliases
+ for the wanted commands, and only them for us to get a large security
+ wall.
+}]
+
+[section {Wire Protocol Version 3}]
+[subsection {Basic Layer}]
+
+The basic encoding for [emph all] data is UTF-8. Because of this
+binary data, including the NULL character, can be sent over the wire
+as is, without the need for armoring it.
+
+[subsection {Basic Message Layer}]
+
+On top of the [sectref {Basic Layer}] we have a
+
+[term {message oriented}] exchange of data.
+
+The totality of all characters written to the channel is a Tcl list,
+with each element a separate [term message], each itself a list. The
+messages in the overall list are separated by EOL. Note that EOL
+characters can occur within the list as well. They can be
+distinguished from the message-separating EOL by the fact that the
+data from the beginning up to their location is not a valid Tcl list.
+
+[para]
+
+EOL is signaled through the linefeed character, i.e [const LF], or,
+hex [const 0x0a]. This is following the unix convention for
+line-endings.
+
+[para]
+
+As a list each message is composed of [term words]. Their meaning
+depends on when the message was sent in the overall exchange. This is
+described in the upcoming sections.
+
+[subsection {Negotiation Messages - Initial Handshake} ih]
+
+The command protocol is defined like this:
+
+[list_begin itemized]
+[item]
+
+The first message send by a client to a server, when opening the
+connection, contains two words. The first word is a list as well, and
+contains the versions of the wire protocol the client is willing to
+accept, with the most preferred version first. The second word is the
+TCP port the client is listening on for connections to itself. The
+value [const 0] is used here to signal that the client will not listen
+for connections, i.e. that it is purely for sending commands, and not
+receiving them.
+
+[item]
+
+The first message sent by the server to the client, in response to the
+message above contains only one word. This word is a list, containing
+the string [const vers] as its first element, and the version of the
+wire protocol the server has selected from the offered versions as the
+second.
+
+[comment {
+ NOTE / DANGER
+
+ The terminating EOL for this first response will be the socket
+ specific default EOL (Windows/Internet convention: "\r\n").
+ However all future messages use Unix convention, i.e. "\n",
+ for their EOLs, embedded or terminating.
+
+ Reason: The internal command commNewComm does the common
+ processing for new connections, doing the
+
+ fconfigure -translation lf
+
+ However the handshake response containing the accepted
+ version is sent before commNewComm is called (in
+ commIncoming).
+
+ NOTE 2:
+
+ This inconsistency has been fixed locally already, but
+ not been committed yet.
+}]
+[list_end]
+
+[subsection {Script/Command Messages}]
+
+All messages coming after the [sectref ih {initial handshake}]
+consist of three words. These are an instruction, a transaction id,
+and the payload. The valid instructions are shown below. The
+transaction ids are used by the client to match any incoming replies
+to the command messages it sent. This means that a server has to copy
+the transaction id from a command message to the reply it sends for
+that message.
+
+[list_begin definitions]
+
+[def [const send]]
+[def [const async]]
+[def [const command]]
+
+The payload is the Tcl script to execute on the server. It is actually
+a list containing the script fragments. These fragment are
+
+[cmd concat]enated together by the server to form the full script to
+execute on the server side.
+
+This emulates the Tcl "eval" semantics.
+
+In most cases it is best to have only one word in the list, a list
+containing the exact command.
+
+[para]
+Examples:
+[para]
+[example {
+ (a) {send 1 {{array get tcl_platform}}}
+ (b) {send 1 {array get tcl_platform}}
+ (c) {send 1 {array {get tcl_platform}}}
+
+ are all valid representations of the same command. They are
+ generated via
+
+ (a') send {array get tcl_platform}
+ (b') send array get tcl_platform
+ (c') send array {get tcl_platform}
+
+ respectively
+}]
+[para]
+
+Note that (a), generated by (a'), is the usual form, if only single
+commands are sent by the client.
+
+For example constructed using [cmd list], if the command contains
+variable arguments. Like
+
+[para]
+[example {
+ send [list array get $the_variable]
+}]
+[para]
+
+These three instructions all invoke the script on the server
+side. Their difference is in the treatment of result values, and thus
+determines if a reply is expected.
+
+[list_begin definitions]
+[def [const send]]
+
+A reply is expected. The sender is waiting for the result.
+
+[def [const async]]
+
+No reply is expected, the sender has no interest in the result and is
+not waiting for any.
+
+[def [const command]]
+
+A reply is expected, but the sender is not waiting for it. It has
+arranged to get a process-internal notification when the result
+arrives.
+
+[list_end]
+
+[def [const reply]]
+
+Like the previous three command, however the tcl script in the payload
+is highly restricted.
+
+It has to be a syntactically valid Tcl [cmd return] command. This
+contains result code, value, error code, and error info.
+
+[para]
+Examples:
+[para]
+[example {
+ {reply 1 {return -code 0 {}}}
+ {reply 1 {return -code 0 {osVersion 2.4.21-99-default byteOrder littleEndian machine i686 platform unix os Linux user andreask wordSize 4}}}
+}]
+
+[list_end]
+
+[comment {
+ Socket Miscellanea
+ ------------------
+
+ It is possible to have two sockets between a client and a
+ server. This happens if both sides connected to each other at
+ the same time.
+
+ Current protocol versions
+ -------------------------
+
+ V2
+
+ V3 This is preferred version and uses UTF 8 encoding.
+
+ This is actually the only version which will work IIU
+ the code right. Because the server part of comm will
+ send the version reply if and only if version 3 was
+ negotiated.
+
+ IOW if v2 is used the client will not see a version
+ reply during the negotiation handshake.
+}]
+
+[vset CATEGORY comm]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/comm/pkgIndex.tcl b/tcllib/modules/comm/pkgIndex.tcl
new file mode 100644
index 0000000..b0372e1
--- /dev/null
+++ b/tcllib/modules/comm/pkgIndex.tcl
@@ -0,0 +1,2 @@
+if {![package vsatisfies [package provide Tcl] 8.3]} {return}
+package ifneeded comm 4.6.3.1 [list source [file join $dir comm.tcl]]