#!/bin/bash help () { cat <<- EOF >&2 Usage: iocsh [options] [files] [macro=value] ... Start an EPICS iocsh and load files Options: -?, -h, --help Show this page and exit. -v, --version Show version and exit. -win, --win Run Windows softIOC via wine. -32 Force 32 bit version (on 64 bit systems). --nopva Do not use PVA (in EPICS 7). --nolock Do not lock memory into RAM (in EPICS 3.15.3+). -x[.z[.y]] Select EPICS base version x.z.y (e.g. 3.14.8, 3.15, 7). -d, --debug Run IOC with gdb. -dv Run IOC with valgrind. -dp Run IOC with perf record. -i bashscript Source bashscript before starting ioc. -c 'cmd args' Ioc shell command. -s 'prog m=v' Sequencer program (and arguments), run with 'seq'. This forces an 'iocInit' before running the program. -r module[,ver] Module (optionally with version) loaded via 'require'. -n name Name of the IOC, used for prompt and \${IOC} variable. Default: dirname if parent dir is "ioc" otherwise hostname. @file More arguments are read from file. Supported filetypes: *.db, *.dbt, *.template loaded via 'dbLoadRecords' *.subs, *.subst loaded via 'dbLoadTemplate' *.dbd loaded via 'dbLoadDatabase' *.so loaded via 'dlload' (or 'ld' before 3.14.12) All other files are executed as startup scripts by the EPICS shell. After a file you can specify substitutions like m1=v1 m2=v1 for that file. If a readable file iocsh.init exists in ./, cfg/, or /etc/, it is sourced when iocsh starts (see also -i option). Only the first one found is sourced. This allows to set environment variables etc before EPICS starts. In this context, the variables \${IOC}, \${BASE}, and \${EPICS_HOST_ARCH} are already set to the ioc name, the path to the used EPICS installation and the used architecture (which may have been modified by -32 or -win). If the environment variable NOPVA is not empty, it works like --nopva except if set to 0, n, no, f, false or off (case insensitive). The environment variable LOADER can be used to run EPICS inside another program. Internally this is used for wine, gdb, valgrind, etc. A file EPICSVERSION or cfg/EPICSVERSION may contain a default EPICS version like 3.14.8, 3.15, or 7 but the command line option takes precedence. Examples: iocsh st.cmd iocsh my_database.template P=XY M=3 iocsh -r my_module,version -c 'initModule()' iocsh -3.15.4 -dp st.cmd iocsh -c 'var requireDebug 1' st.cmd EOF exit } version () { { echo "iocsh by Dirk Zimoch" } >&2 exit } # realpath and readlink are not available on all systems, let's try what works... rp() { ( realpath "$1" || readlink -f "$1" || readlink "$1" || (cd -P "$1" && echo $PWD) || (x=$(\ls -ld "$1") && echo ${x##* }) || echo $1 ) 2>/dev/null } # if EPICS_HOST_ARCH is not set guess it if [ -z "$EPICS_HOST_ARCH" ] then EPICS_HOST_ARCH=$(basename $(dirname $(rp $(which caRepeater 2>/dev/null)) 2>/dev/null) 2>/dev/null) if [ -n "$EPICS_HOST_ARCH" ] then echo "Guessing EPICS_HOST_ARCH=$EPICS_HOST_ARCH." >&2 else echo "EPICS_HOST_ARCH is not set." >&2 echo "(And I cannot guess it because I cannot find caRepeater.)" >&2 exit 1 fi fi if read BASE < EPICSVERSION || read BASE < cfg/EPICSVERSION then unset EPICS_BASE fi 2> /dev/null # IOC name derives from hostname # (trailing possible '\r' under cygwin) IOC=$(hostname|tr -d '\r') # trailing possible domain name IOC=${IOC%%.*} # or get IOC name from start directory following PSI convention D=$(basename $(dirname $PWD)) if [ "${D#*_}" = "ioc" ] then IOC=$(basename $PWD) fi export IOC while true do case $1 in ( -win | --win ) PATH=/opt/gfa-wine/bin:$PATH LOADER="$LOADER wine64" export WINEDEBUG=fixme-all,err-all export WINEDLLOVERRIDES="mscoree,mshtml=" export WINEPREFIX=$HOME/.wine-$EPICS_HOST_ARCH [[ $(wine64 --version) = wine-6.* ]] && stty -isig 2> /dev/null # or CTRL-C hangs up on wine 6 EPICS_HOST_ARCH=windows-x64 ;; ( -32 ) EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64} ;; ( -[1-9]* ) unset EPICS_BASE BASE=${1#-} ;; ( -nopva | --nopva ) NOPVA=1 ;; ( -nolock | --nolock ) NOLOCK=1 ;; ( -n | --name ) shift IOC="$1" ;; ( -d | -dg | --debug ) LOADER="gdb --eval-command run --args $LOADER" ;; ( -dv ) LOADER="valgrind --leak-check=full $LOADER" ;; ( -dp ) LOADER="perf record $LOADER" ;; ( -h | "-?" | -help | --help ) help ;; ( -v | -ver | --ver | -version | --version ) version ;; ( * ) break ;; esac shift done # Either EPICS or EPICS_BASE should be set to the install directory if [ -z "$EPICS_BASE" ] then if [ -z "$EPICS" ] then # look for some standard install directories for EPICS in /usr/local/epics /opt/epics /epics do if [ -d "$EPICS" ] then break fi done if [ ! -d "$EPICS" ] then EPICS=$(dirname $(dirname $(dirname $(dirname $(ldd $(which caRepeater) | awk '/libca/ {print $3}'))))) echo "Guessing EPICS=$EPICS" fi if [ ! -d "$EPICS" ] then echo "Cannot find EPICS installation directory." >&2 echo "Try setting EPICS environment variable." >&2 exit 1 fi fi if [ -z "$BASE" ] then # if no BASE is given, first look for plain /epics/base/ dir EPICS_BASE=$(\ls -1vrd $EPICS/base/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1) fi if [ -z "$EPICS_BASE" ] then # look for exact requested EPICS version EPICS_BASE=$(\ls -1vrd $EPICS/base-$BASE/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1) if [ -z "$EPICS_BASE" ] then # find highest (requested) 3-part-numeric EPICS version that supports our architecture (or its 32 bit version) # ignore all versions listed in ignore file EPICS_BASE=$(eval \ls -1vrd $EPICS/base-$BASE*/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null \ $(test -f $EPICS/ignore && sed < $EPICS/ignore ':b;$!N;s/[[:space:]]/|/;tb;s/\./\\./g;s/.*/|grep -Ev \x27&\x27/g') \ | grep -E '/base-[0-9]+\.[0-9]+\.[0-9]+/' \ | head -n1) fi fi if [ -z "$EPICS_BASE" ] then if [ -z "$(\ls -1vrd $EPICS/base-$BASE*/ 2>/dev/null)" ] then echo No EPICS $BASE installation found $(test -n "$EPICS" && echo -n " in $EPICS").>&2 exit 1 fi echo No EPICS $BASE available for EPICS_HOST_ARCH=$EPICS_HOST_ARCH. >&2 test -f $EPICS/ignore && echo "(ignored: $(sed < $EPICS/ignore 's/[[:space:]]/, /g') as listed in $EPICS/ignore)" exit 1 fi # maybe we need to change from 64 bit to 32 bit if [ "$EPICS_HOST_ARCH" != "${EPICS_BASE#*/bin/}" ] then EPICS_HOST_ARCH=${EPICS_BASE#*/bin/} echo "No 64 bit version in ${EPICS_BASE%bin*}." >&2 echo "Switching to 32 bit version $EPICS_HOST_ARCH." >&2 fi EPICS_BASE=$(rp ${EPICS_BASE%bin*}) fi if [ ! -d "$EPICS_BASE" ] then echo "Cannot find EPICS_BASE directory." >&2 echo "Try setting EPICS_BASE environment variable to full path" >&2 exit 1 fi LIBDIR=lib LIBPREFIX=lib LIBPOSTFIX=.so EXEPOSTFIX= case $(uname) in ( Darwin ) LIBPOSTFIX=.dylib ;; esac case "$EPICS_HOST_ARCH" in ( win* ) LIBDIR=bin LIBPREFIX= LIBPOSTFIX=.dll EXEPOSTFIX=.exe ;; # Some architectures have little memory ( mvl40-xscale_be | moxa42-armv6l | eldk42-ppc4xxFP ) # Safe memory by not using PVA NOPVA=1 # Do not lock memory in RAM to save on VmRSS NOLOCK=1 ;; esac # Get actual EPICS BASE version, either from CONFIG_BASE_VERSION (text) file or from version string in libCom.so # Version may have 3 or 4 digits. We make a (4*2 digit) BASECODE too for easier comparison. # How many digits the drivers use is another question. if [ -f "$EPICS_BASE/configure/CONFIG_BASE_VERSION" ] then eval $(awk -F '[ \t]*=[ \t]*' ' /^[ \t]*EPICS_VERSION[ \t]*=/ {v=$2} /^[ \t]*EPICS_REVISION[ \t]*=/ {r=$2} /^[ \t]*EPICS_MODIFICATION[ \t]*=/ {m=$2+0} /^[ \t]*EPICS_PATCH_LEVEL[ \t]*=/ {p=$2+0} END {print "BASE3="v"."r"."m";BASE4="v"."r"."m"."p";BASECODE="v*1000000+r*10000+m*100+p} ' < $EPICS_BASE/configure/CONFIG_BASE_VERSION) elif [ -f "$EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX" ] then strings $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX | grep "EPICS R[0-9]" eval $(strings $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX | awk -F'[.R]' ' /EPICS R[0-9]/ {print "BASE3="$2"."$3"."$4+0";BASE4="$2"."$3"."$4+0"."$5+0";BASECODE="$2*1000000+$3*10000+$4*100+$5 }') else echo "Cannot guess EPICS base version." >&2 exit 1; fi # Check how many digits of BASE we need to find the drivers for B in $BASE $BASE4 $BASE3 ${EPICS_BASE#*/base-} do if [ -d "${EPICS_MODULES:=/ioc/modules}/${REQUIRE:=require}" ] then # new module pool model REQUIRE_LIB=$(ls -1rv $EPICS_MODULES/$REQUIRE/${REQUIRE_VERSION:-*.*.*}/R$B/lib/$EPICS_HOST_ARCH/$LIBPREFIX$REQUIRE$LIBPOSTFIX 2>/dev/null | head -n 1) REQUIRE_DBD=${REQUIRE_LIB%/lib/*}/dbd/$REQUIRE.dbd fi if [ -n "$REQUIRE_LIB" ] then BASE=$B break fi done # Check for 64 bit versions, default to 32 bit if [ ! -d "$EPICS_BASE/bin/${EPICS_HOST_ARCH}" -a -d "$EPICS_BASE/bin/${EPICS_HOST_ARCH%_64}" ] then echo "No 64 bit EPICS installation found. Defaulting to 32 bit" >&2 EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64} fi export EPICS_HOST_ARCH # setup search path for require ODIR=O.${BASE}_$EPICS_HOST_ARCH EPICS_DRIVER_PATH=.:bin/$EPICS_HOST_ARCH:bin:snl:../snl:$ODIR:src/$ODIR:snl/$ODIR:../snl/$ODIR:${EPICS_DRIVER_PATH#:}:${EPICS_MODULES} export EPICS_DRIVER_PATH # setup search PATH (Windows needs it for libraries, but also find helper programs like msi) PATH=$PATH:$EPICS_BASE/bin/$EPICS_HOST_ARCH:$EPICS_BASE/../seq/bin/$EPICS_HOST_ARCH export EPICS_IOCSH_HISTFILE= # Call init script after IOC name, EPICS_HOST_ARCH and EPICS_BASE are set. for dir in . cfg /etc do if [ -r "$dir/iocsh.init" ] then source $dir/iocsh.init break fi done loadFiles () { while [ "$#" -gt 0 ] do file=$1 case $file in ( -[1-9]* | -32 | -nopva | --nopva | -win | --win | -d | -dg | --debug | -dv | -dp | -n | --name ) echo "Option $file must be used earlier" >&2 exit 1 ;; ( -h | "-?" | -help | --help ) help ;; ( -v | -ver | --ver | -version | --version ) version ;; ( @* ) loadFiles $(cat ${file#@}) ;; ( -i ) shift source $1 ;; ( -c ) shift case $1 in ( seq* ) if [ "$init" != NO ] then echo "iocInit" init=NO fi ;; ( iocInit ) init=NO ;; esac echo $1 ;; ( -s ) shift if [ "$init" != NO ] then echo "iocInit" init=NO fi echo "seq $1" ;; ( -r ) shift echo "require $1" ;; ( -* ) echo "Unknown option $1" >&2 echo "Try: $(basename $0) --help" >&2 exit 1 ;; ( *$LIBPOSTFIX ) if [ "$BASECODE" -ge 3141200 ] then echo "dlload \"$file\"" else echo "ld \"$file\"" fi ;; ( *=* ) echo -n $file | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}' ;; ( * ) subst="" while [ "$#" -gt 1 ] do case $2 in ( *=* ) subst="$subst,$2"; shift ;; ( * ) break ;; esac done subst=${subst#,} case $file in ( *.db | *.template) echo "dbLoadRecords '$file','$subst'" ;; ( *.subs | *.subst ) echo "dbLoadTemplate '$file','$subst'" ;; ( *.dbd ) # some dbd files must be loaded before main to take effect echo "dbLoadDatabase '$file','$DBD','$subst'" ;; ( * ) if [ "$BASECODE" -ge 3150000 ] then echo "iocshLoad '$file','$subst'" else echo -n $subst | awk -F '=' -v 'RS=,' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}' echo "< '$file'" fi if grep -q iocInit $file; then init=NO; fi ;; esac ;; esac shift done } startup=${IOCSH_TMP:-/tmp}/iocsh.startup.$$ # clean up and kill the softIoc when killed by any signal # execute any exiting exit trap (from init scripts) as well trap "stty sane 2> /dev/null;echo;rm -f $startup;$(trap -p EXIT | sed 's/.*\x27\(.*\)\x27.*/\1; /')kill -s SIGTERM 0" EXIT { echo "# date=\"$(date)\"" echo "# user=\"${USER:-$(whoami)}\"" for var in IOC PWD BASE EPICS_HOST_ARCH SHELLBOX EPICS_CA_ADDR_LIST EPICS_DRIVER_PATH PATH LD_LIBRARY_PATH do echo "# $var=\"${!var}\"" done if [ "$BASECODE" -ge 3141200 ] then case $(tr A-Z a-z <<< "$NOPVA") in (0|n|no|f|false|off) unset NOPVA; esac if [ -z "$NOPVA" -a -x "$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIocPVA$EXEPOSTFIX" ] then EXE=$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIocPVA$EXEPOSTFIX ARGS="-D $EPICS_BASE/dbd/softIocPVA.dbd" echo "dlload $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}nt$LIBPOSTFIX" echo "dlload $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}pvDatabase$LIBPOSTFIX" else EXE=$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIoc$EXEPOSTFIX ARGS="-D $EPICS_BASE/dbd/softIoc.dbd" fi LDCMD="dlload" else # get rid of the compiled-in rpath because at PSI that is a link pointing to current EPICS version. LOADER="$LOADER /lib/ld-linux.so.2" LOADERARGS="--library-path $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH --inhibit-rpath ''" APP=ioc EXE=$EPICS_EXTENSIONS/bin/$EPICS_HOST_ARCH/$APP DBD=$EPICS_EXTENSIONS/dbd LDCMD="ld" echo "dbLoadDatabase \"$APP.dbd\",\"$DBD\"" echo "${APP}_registerRecordDeviceDriver(pdbbase)" fi # switch off memoy lock if requested (only for EPICS 3.15.3+) if [ "$BASECODE" -ge 3150300 -a -n "$NOLOCK" ] then echo "var dbThreadRealtimeLock 0" fi # use WINE to run softIo, and convert EPICS_DRIVER_PATH to windows format if [[ "$EXEPOSTFIX" == ".exe" ]] then EPICS_DRIVER_PATH_WIN= while read -d ':' p; do EPICS_DRIVER_PATH_WIN="$EPICS_DRIVER_PATH_WIN;${p/#\//z:/}" done <<< "$EPICS_DRIVER_PATH:" EPICS_DRIVER_PATH=${EPICS_DRIVER_PATH_WIN#;} fi if [ ! -x "$EXE" ]; then echo "$EXE not found or not executable." >&2 exit 1 fi if [ ! -f "$REQUIRE_LIB" ] then echo "Library ${REQUIRE_LIB:-$LIBPREFIX$REQUIRE$LIBPOSTFIX} not found." >&2 echo "Command 'require' is not available." >&2 else echo "$LDCMD $REQUIRE_LIB" echo "dbLoadDatabase $REQUIRE_DBD" echo "${REQUIRE%-*}_registerRecordDeviceDriver" echo "require misc ${MISC_VERSION:-ifexists}" fi loadFiles "$@" if [ "$init" != NO ] then echo "iocInit" fi echo 'epicsEnvSet IOCSH_PS1,"${IOC}> "' } > $startup echo $EXE $ARGS $startup #enable core dumps ulimit -c unlimited eval "$LOADER $LOADERARGS $EXE" $ARGS "$startup" 2>&1 STATUS=$? exit $STATUS