hkoba Basic idea:
ssh has port forwarding. It is ready-to-run MUX (multiplexer). With this, we can avoid re-inventing a MUX protocol on single ssh stream. Also, we already have proven package for script remoting:
comm.
So, I simply glued these two.
2012-11-11: Now revised version of sshcomm is available on
github.
#!/usr/bin/tclsh
# -*- mode: tcl; tab-width: 8 -*-
# $Id: 13351,v 1.7 2005-12-19 07:01:24 jcw Exp $
#
# Usage:
#
# set num [sshcomm::client::create $host]
# comm::comm send $num {script...}
package provide sshcomm 0.1
namespace eval ::sshcomm {
namespace eval server {}
namespace eval client {
variable SCRIPT [info script]
}
}
proc ::sshcomm::definition-of-proc {proc} {
set args {}
foreach var [info args $proc] {
if {[info default $proc $var default]} {
lappend args [list $var $default]
} else {
lappend args $var
}
}
list proc $proc $args [info body $proc]
}
proc ::sshcomm::definition {{ns {}}} {
if {$ns == ""} {
return [definition [namespace current]]
} else {
set result {}
append result [list namespace eval $ns {}]\n
foreach proc [info procs [set ns]::*] {
append result [definition-of-proc $proc]\n
}
foreach ns [namespace children $ns] {
# puts "ns=$ns"
append result [definition $ns]\n
}
set result
}
}
proc ::sshcomm::sshcmd {} {
set cmdName [namespace current]::sshcmd/$::tcl_platform(platform)
if {[info procs $cmdName] != ""} {
$cmdName
} else {
return "ssh -T"
}
}
proc ::sshcomm::sshcmd/windows {} {
return plink
}
#########################################
# Server
#
proc ::sshcomm::create-comm {port listen args} {
lappend args -port $port -listen $listen
if {[info exists ::comm::comm]} {
eval [list ::comm::comm config] $args
} else {
namespace eval ::comm {variable comm; array set comm {comm,port 0}}
package require comm
unset ::comm::comm(comm,port)
eval [list ::comm::comm new ::comm::comm] $args
}
}
proc ::sshcomm::server::create {port args} {
::sshcomm::create-comm $port 1
puts "OK port $port"
fileevent stdin readable [list [namespace current]::terminator stdin]
keepalive
vwait [namespace current]::forever
}
proc ::sshcomm::server::keepalive {{sec 30}} {
puts [clock seconds]
variable keepalive_id [after [expr {$sec * 1000}] \
[namespace code [info level 0]]]
}
proc ::sshcomm::server::terminator {fh args} {
set count [gets $fh line]
if {$count < 0} {
close $fh
exit
}
if {$count > 0} {
uplevel \#0 $line
}
}
#########################################
# Client
#
proc ::sshcomm::client::probe-available-port host {
package require comm
# To recycle local listener port
set local [comm::comm self]
comm::comm destroy
::comm::comm new ::comm::comm
eval [list lappend cmd] [sshcomm::sshcmd]
lappend cmd $host tclsh
lappend cmd << {
package require comm
puts [comm::comm self]
}
set remote [eval [list exec] $cmd]
list $local $host $remote
}
# ::sshcomm::client::create --
#
#
#
# Arguments:
# host remote hostname.
#
# Results:
# comm id.
proc ::sshcomm::client::create host {
set forward [probe-available-port $host]
if {[llength $forward] != 3} {
error "Can't detect available ports for $host"
}
eval [list create-forward] $forward
}
proc ::sshcomm::client::create-forward {lport host rport} {
set fh [connect $lport $host $rport]
setup-server $fh $rport
wait-server $lport $fh $rport
}
proc ::sshcomm::client::connect {lport host rport} {
variable $lport; upvar 0 $lport data
set cmd "| [sshcomm::sshcmd] -L $lport:localhost:$rport $host"
append cmd " tclsh"
set fh [open $cmd w+]
fconfigure $fh -buffering line
array set data [list fh $fh host $host rport $rport]
set fh
}
proc ::sshcomm::client::setup-server {fh rport} {
puts $fh {
fconfigure stdout -buffering line
fconfigure stderr -buffering line
}
puts $fh [sshcomm::definition]
puts $fh {}
puts $fh [list ::sshcomm::server::create $rport]
flush $fh
}
proc ::sshcomm::client::wait-server {lport fh rport} {
if {[gets $fh line] <= 0} {
error "Can't invoke sshcomm!"
}
if {$line != "OK port $rport"} {
error "Unknown result: $line"
}
fileevent $fh readable [list gets $fh [namespace current]::last-click]
comm::comm connect $lport
set lport
}
proc ::sshcomm::client::last-click {} {
variable last-click
set last-click
}
proc ::sshcomm::fread {fn} {
set fh [open $fn]
set data [read $fh]
close $fh
set data
}