EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  <20082009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  <20082009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: Ethernet-to-RS232
From: Dirk Zimoch <[email protected]>
To: [email protected]
Date: Thu, 10 Jul 2008 10:19:35 +0200
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  <20082009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
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  <20082009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
ANJ, 02 Sep 2010 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·