Experimental Physics and Industrial Control System
Hi,
We are using the attached Tcl program (shellbox.tcl) to wrap ioc shells on
Linux. It requires the "expect" package. It allows any number of users to
connect at the same time using telnet. (Without password, thus better do not run
it on an open network.)
It also logs shell output. However, at the moment, scrolling back the output is
not yet implemented.
We use it in combination with the also attached init script (shellbox) to start
ioc shells as demons. The init script reads a config file (/etc/shellbox.conf)
which looks like this:
#shellbox configuration file
#Starts commands inside a "box" with a telnet-like server.
#Contact the shell with: telnet <hostname> <port>
#Syntax:
#port user directory command args
50000 zimoch /ioc/TEST-IOC-DIRK iocsh startup.cmd
Dirk
Rod Nussbaumer wrote:
Eric Norum wrote:
We have an 'iocConsole' script which ssh's to a common machine as a
common user (controlled by ssh public keys) and connects to a screen
session if it is active and otherwise starts a screen session. The
screen session multiplexes access to the single serial line. This
deals with most of your concerns, I believe.
1) We can control access without having to deal with IT
administrators (other than setting up the common user account to
begin with) -- just add a public key to the common user account. 2)
Multiple users can access a serial line session at the same time. 3)
Persistent scrollback, even if no users are currently attached.
Eric.
We are presently using a system developed in-house, but based
heavily on your description of your own iocConsole application. It works
fine, but concentrates all access through a single host, and
requires maintenance to add and delete IOCs, etc. I think it would be
nice and simple to just open an SSH or telnet session from wherever you
are. Having both systems operate concurrently would be the best of all
worlds.
--- Rod.
--
Dr. Dirk Zimoch
Paul Scherrer Institut, WBGB/006
5232 Villigen PSI, Switzerland
Phone +41 56 310 5182
#!/usr/bin/tclsh
# shellbox
#
# This program serves as a box that runs a shell in it.
# stdout and stdin of the shell are connected to the box.
# If the shell uses stderr, you should wrap it in a script
# that redirects stderr to stdout.
#
# Whenever the shell dies, it will be restarted.
#
# Clients can connect to the box via TCP (e.g. with telnet).
# Any number of clients can connect at the same time. Input
# of all clients is merged. Output of the shell is broadcasted
# to all clients and to stdout of the box.
#
# When started with the -paranoid option, the box only accepts
# connections from localhost.
#
# The box intercepts the following commands:
# exit or quit disconnect from the box
# clients gives a list of all connected clients
# kill terminates the shell
#
package req Expect
log_user 0
set timeout -1
regsub -all {\$} {shellbox by Dirk Zimoch
$Source: /cvs/CVSROOT/UTILITIES/scripts/tcl/shellbox,v $
$Revision: 1.6 $
$Date: 2003/06/26 12:55:49 $} {} version
proc usage {} {
puts "usage: shellbox \[options\] port command args"
puts "options: -help : show this text"
puts " -version : show cvs release"
puts " -paranoid : allow connections from localhost only"
puts " -dir directory : set working directory"
}
proc createServer {port} {
global paranoid
if [catch {
if $paranoid {
socket -server connectionHandler -myaddr localhost $port
} else {
socket -server connectionHandler $port
}
} msg] {
puts stderr "shellbox: Can't install server on port $port"
puts stderr $msg
exit 2
}
puts "server started on port $port"
}
proc bgerror {args} {
global errorInfo
catch {puts stderr "shellbox: $errorInfo"}
}
set channels {}
proc connectionHandler {channel addr port} {
global channels command clientinfo
set peer [fconfigure $channel -peername]
set clientinfo($channel) [lindex $peer 1]:[lindex $peer 2]
fconfigure $channel -blocking no -buffering none
# do some telnet magic to change from line-mode to char-mode
puts -nonewline $channel "\xff\xfb\x03\xff\xfb\x01"
fileevent $channel readable "inputHandler $channel"
forwardOutput "**** new client $clientinfo($channel) ****\n"
catch {puts $channel "**** '$command' running ****"}
lappend channels $channel
catch {puts $channel "clients:\n[getClientlist]"}
send "\n"
}
proc closeChannel {channel} {
global channels clientinfo
catch {puts $channel "**** see you later ****"}
set index [lsearch $channels $channel]
set channels [lreplace $channels $index $index]
forwardOutput "**** client $clientinfo($channel) logged out ****\n"
close $channel
unset clientinfo($channel)
}
proc inputHandler {channel} {
global echo
set data [read $channel]
if [eof $channel] {
closeChannel $channel
return
}
binary scan $data H* hex
switch -glob -- $hex {
03 {closeChannel $channel}
04 {closeChannel $channel}
18 {killProgram}
fff4fffd06 {closeChannel $channel}
ffedfffd06 {closeChannel $channel}
ff* {puts "ignored $data"}
default {send -- $data}
}
}
proc forwardOutput {data {exclude ""}} {
global channels
catch {puts -nonewline $data}
foreach channel $channels {
if {$channel != $exclude} {
catch {puts -nonewline $channel $data}
}
}
}
proc getClientlist {} {
global channels clientinfo
set result ""
foreach channel $channels {
append result "$clientinfo($channel)\n"
}
return $result
}
set diedyoung 1
proc startProgram {} {
global command spawn_id diedyoung directory
# catch {close $spawn_id}
if {[catch {cd $directory} msg]} {
forwardOutput "**** $msg (trying later) ****\n"
while {[catch {cd $directory}]} { after 10000 }
}
after 1000 set diedyoung 0
if [catch {
eval spawn $command
} msg] {
catch {puts stderr "shellbox: $msg"}
exit 3
}
forwardOutput "**** '$command' started in [pwd] ****\n"
expect_background {
kill {
killProgram
}
? {
forwardOutput $expect_out(buffer)
}
eof {
puts "EOF received"
if ($diedyoung) {
forwardOutput $expect_out(buffer)
forwardOutput "**** first run of '$command' died within first 10 seconds. I will quit. ****\n"
exit 4
}
forwardOutput $expect_out(buffer)
forwardOutput "**** '$command' died ****\n"
after idle startProgram
}
}
}
proc killProgram {} {
global command
forwardOutput "**** killing '$command' ****\n"
exec kill -s SIGKILL [exp_pid]
wait
forwardOutput "**** '$command' killed ****\n"
}
set paranoid 0
set directory .
while {[string match -* $argv]} {
switch -exact -- [lindex $argv 0] {
"-?" - "-help" { usage; exit 0 }
"-v" - "-version" {puts $version; exit 0}
"-paranoid" {set paranoid 1}
"-echo" {puts stderr "shellbox: option -echo is obsolete"}
"-dir" {
set directory [lindex $argv 1]
set argv [lrange $argv 2 end]
continue
}
default {puts stderr "shellbox: unknown option [lindex $argv 0]"; usage; exit 1}
}
set argv [lrange $argv 1 end]
}
set port [lindex $argv 0]
set command [lrange $argv 1 end]
if {![regexp {^[0-9]+$} $port] || [llength $command] == 0} {
usage; exit 1
}
fconfigure stdout -buffering none
startProgram
createServer $port
vwait forever
#!/bin/bash
# chkconfig: 2345 98 2
# description: shellbox service for spawning programs
. /etc/rc.d/init.d/functions
export PATH=$PATH:/usr/local/bin
. /etc/profile
export SLSBASE=/work
export INSTBASE=/work
export HOSTNAME=$(hostname -s)
exe=/usr/local/bin/shellbox.tcl
prog=shellbox
params=
conf=/etc/shellbox.conf
logdir=/var/log/$prog
shells=/var/run/$prog
[ -n "$logdir" -a -d $logdir ] || mkdir $logdir
fail () {
echo -n $@
failure
echo
exit 1
}
launch () {
if [ "$1" = "-reload" ]
then
reload=YES
shift
fi
temp=$(mktemp -p $(dirname $shells)) || fail "can't create temporary file"
while read PORT USER DIR COMMAND
do
# check for empty lines and comments
[[ $PORT == "" || $PORT == \#* ]] && continue
# check if already started shell is still alive
if LINE=$(grep "$PORT $USER $DIR $COMMAND" $shells 2> /dev/null)
then
PID=${LINE%% *}
if checkpid $PID
then
if [ -z "$reload" ] && [ -z "$*" ] || echo "$*" | grep -qw $PORT
then
echo_warning
echo "Already running: $PORT $USER $DIR $COMMAND"
fi
echo $LINE >> $temp
continue
fi
fi
# check if we have to start all shells or only this PORT
[ "$*" ] && echo "$*" | grep -qwv $PORT && continue
if [ -n "$logdir" ]
then
LOG=$logdir/$PORT
rm -f $LOG
else
LOG=/dev/null
fi
# start shellbox as other user
echo -n Starting: $PORT $USER $DIR $COMMAND
export SHELLBOX=$HOSTNAME:$PORT
export HOME=$(getent passwd $USER | awk -F : '{print $6}')
sudo -u $USER $exe -dir $DIR $params $PORT $COMMAND >> $LOG 2>&1 < /dev/null &
PID=$!
# check if starting worked or failed
sleep 0.1
if checkpid $PID
then
echo "$PID $PORT $USER $DIR $COMMAND" >> $temp
echo
else
echo_failure
echo
cat $LOG
fi
done < $conf
mv $temp $shells
chmod 0644 $shells
}
start () {
[ -r $conf ] || fail "$conf not readable"
[ -x $exe ] || fail "$exe is not executable"
launch $*
touch /var/lock/subsys/$prog
}
stopshell() {
PID=$1
PORT=$2
shift
echo -n Stopping: $*
kill $PID 2> /dev/null || echo_failure
echo
if [ $logdir ]
then
echo -e "\n**** stopped ****" >> $logdir/$PORT
fi
}
stop () {
# anything to stop?
if [ ! -r $shells ]
then
echo -n "$prog: No shells started."
success
echo
exit 0
fi
if [ -z "$1" ]
then
# kill all shellboxes
while read PID PORT ARGS
do
stopshell $PID $PORT $ARGS
done < $shells
rm -f $shells
rm -f /var/lock/subsys/$prog
else
# kill only selected shellboxes
temp=$(mktemp -p $(dirname $shells)) || fail "can't create temporary file"
while read PID PORT ARGS
do
echo "$*" | grep -qw $PORT && stopshell $PID $PORT $ARGS || echo $PID $PORT $ARGS >> $temp
done < $shells
mv $temp $shells
chmod 0644 $shells
fi
}
reload () {
echo "Reloading $conf: "
[ -r $conf ] || fail "not readable"
# anything to stop?
if [ -r $shells ]
then
#first kill all shells that are not configured any more
temp=$(mktemp -p $(dirname $shells)) || fail "can't create temporary file"
while read PID ARGS
do
while read PORT USER DIR COMMAND
do
if [ "$PORT $USER $DIR $COMMAND" = "$ARGS" ]
then
echo "Keeping: $ARGS"
echo "$PID $ARGS" >> $temp
continue 2
fi
done < $conf
stopshell $PID $PORT $ARGS
done < $shells
mv $temp $shells
chmod 0644 $shells
fi
#now start all new shells
launch -reload
}
status () {
[ -r $conf ] || fail "$conf not readable"
if [ "$1" = "-log" ]
then
log=YES
shift
fi
echo -e "pid\tport\tuser\tdir\t\t\tcommand"
while read PORT USER DIR CMD
do
# check for empty lines and comments
[[ $PORT == "" || $PORT == \#* ]] && continue
# check if we have to report all shells
[ "$*" ] && echo "$*" | grep -wqv $PORT && continue
if [ "$logdir" -a "$log" ]
then
echo "-------------------------------------------------------------------"
fi
if LINE=$(grep "$PORT $USER $DIR $CMD" $shells 2> /dev/null)
then
PID=${LINE%% *}
if checkpid $PID
then
echo -n $PID
else
$SETCOLOR_FAILURE
echo -n DEAD
$SETCOLOR_NORMAL
fi
else
$SETCOLOR_FAILURE
echo -n STOPPED
$SETCOLOR_NORMAL
fi
echo -e "\t$PORT\t$USER\t$DIR\t$CMD"
if [ "$logdir" -a "$log" ]
then
grep '\*\*\*\*' $logdir/$PORT 2>/dev/null
fi
done < $conf
}
CMD=$1
shift
case "$CMD" in
(start) start $*;;
(stop) stop $*;;
(restart) stop $*; start $*;; # kill all shells, then start again
(reread|reload) reload $*;; # reload shellbox.conf without killing too much
(status) status $*;;
(*) echo "Usage: $0 {start [ports]|stop [ports]|restart [ports]|reload|status [-log] [ports]}" ;;
esac
- References:
- Ethernet-to-RS232 Dave Reid
- Re: Ethernet-to-RS232 Mauro Giacchini
- RE: Ethernet-to-RS232 Rees, NP (Nick)
- Re: Ethernet-to-RS232 David Kline
- Re: Ethernet-to-RS232 Rod Nussbaumer
- Re: Ethernet-to-RS232 Matthieu Bec
- Re: Ethernet-to-RS232 Maren Purves
- Re: Ethernet-to-RS232 Eric Norum
- Re: Ethernet-to-RS232 Rod Nussbaumer
- Navigate by Date:
- Prev:
Re: Ethernet-to-RS232 Ralph Lange
- Next:
Introducing CAML Tom Pelaia
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
<2008>
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
- Navigate by Thread:
- Prev:
Re: Ethernet-to-RS232 Ralph Lange
- Next:
Engineering and Instrumentation Section lead job at SLAC Chestnut, Ronald P.
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
<2008>
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025