#!/bin/bash # Author: Dirk Zimoch version () { echo '$Author: zimoch $' >&2 echo '$Date: 2018/07/06 12:06:44 $' >&2 echo '$Revision: 1.26 $' >&2 echo '$Source: /cvs/G/EPICS/App/scripts/externalLinks,v $' >&2 exit 1 } usage () { echo "usage: externalLinks [options] " >&2 echo "returns list of link targets that are not resolved internally" >&2 echo "optionally returns list of required modules" >&2 echo "also does some plausibility checks" >&2 echo "options are:" >&2 echo " -h | -? | -help print this text and exit" >&2 echo " -v | -version print version and exit" >&2 echo " -d | -debug print additional debug messages" >&2 echo " -w | -where show record.link and target" >&2 echo " -r | -require show required modules" >&2 echo " -o | -overwrite don't warn about overwriting fields" >&2 echo " -f | -find record find links to record" >&2 echo " -I find templates in dir" >&2 echo " -3.13 use lastest EPICS 3.13 version (default for SLS)" >&2 echo " -3.14 use lastest EPICS 3.14 version (default for SwissFEL, HIPA)" >&2 echo " -3.x.x use specific EPICS version" >&2 echo " -- next argument is file, even if starting with -" >&2 exit 1 } debug () { test "$DEBUG" && echo "$@" > /dev/stderr } shopt -s nullglob shopt -s extglob export LANG=en_US.iso885915 INSTBASE=${INSTBASE%/} INSTBASE=${INSTBASE#/import} case "${INSTBASE}" in ( /work|/prod|/devl ) EPICS=3.13.10 ;; ( /hipa/* ) EPICS=3.14.12 ;; ( * ) EPICS=3.14.12 ;; esac while true do case "$1" in ( -h | -\? | ?(-)-help ) usage ;; ( -v | ?(-)-version ) version ;; ( -d | ?(-)-debug ) DEBUG=1 ;; ( -r | ?(-)-require ) REQUIRE=1 ;; ( -w | ?(-)-where ) WHERE=1 ;; ( -o | ?(-)-overwrite ) OVERWRITE=1 ;; ( -f | ?(-)-find ) FIND+=" $2"; shift ;; ( -f* ) FIND+=" ${1#-f}" ;; ( -I ) INCLUDES+=" -I $2"; shift ;; ( -I* ) INCLUDES+=" -I ${1#-I}" ;; ( ?(-)-[1-9]* ) VERSIONS=$(ls -1dvr /usr/local/epics/base$1*([.0-9]) | head -1) EPICS=${VERSIONS##*-} if [ "$EPICS" = "." ] then echo "No EPICS version ${1##*-} found" >&2 exit 1; fi ;; ( -- | - ) shift; break ;; ( -* ) echo "Unknown option $1 ignored" >&2 ;; ( * ) break ;; esac shift done if [ $# = 0 ] then usage fi echo "Info: Using EPICS version $EPICS" >&2 function expandFile () { if [ ! -r $1 ] then echo "can't read $1" >&2 return fi while read line do case $line in ( include* ) eval expandFile ${line#include} ;; ( * ) echo $line esac done < $1 } { SCAN=dbd/menuScan.dbd if [ ! -r $SCAN ] then SCAN=dbd/scan.dbd fi if [ -r $SCAN ] then echo "# file $SCAN" cat $SCAN fi CONVERT=dbd/slsConvert.dbd if [ ! -r $CONVERT ] then CONVERT=$INSTBASE/epics/base/bin/R${EPICS}/slsConvert.dbd fi if [ -r $CONVERT ] then echo "# file $CONVERT" cat $CONVERT fi MAINDBD=/usr/local/epics/base-${EPICS}/dbd/softIoc.dbd if [ ! -r $MAINDBD ] then MAINDBD=$INSTBASE/iocBoot/R${EPICS}/dbd/base.dbd fi if [ ! -r $MAINDBD ] then echo "can't find base.dbd or softIoc.dbd" >&2 exit 1 fi cat $MAINDBD for dbd in dbd/*.dbd do case $dbd in ( */dbCommon.dbd ) ;; ( */*_conv.dbd ) ;; ( */slsConvert.dbd ) ;; ( */slsMain.dbd ) ;; ( */exampleApp.dbd ) ;; ( */menuScan.dbd ) ;; ( */scan.dbd ) ;; ( * ) echo "# file $dbd"; expandFile $dbd ;; esac done echo $INSTBASE/iocBoot/R${EPICS}/dbd/+([^-]).dbd for modulebase in ${EPICS_MODULES:=/ioc/modules}/* do debug modulebase: $modulebase > /dev/stderr module=$(basename $modulebase) if [ $module = base ] then continue fi debug module: $module > /dev/stderr moduledir=$(ls -1rvd $modulebase/*.*.*/R${EPICS} | head -n 1) [ $moduledir = "." ] && continue debug moduledir: $moduledir > /dev/stderr for dbd in $moduledir/dbd/*.dbd do debug dbd: $dbd > /dev/stderr echo "# file $dbd $module"; cat $dbd declare -A modules[$module]=$dbd done if [ -d $moduledir/db ] then debug includedir: $moduledir/db > /dev/stderr INCLUDES+=" -I $moduledir/db" fi done for dbd in $INSTBASE/iocBoot/R${EPICS}/dbd/+([^-]).dbd do if [ -L $dbd ] then module=$(basename $dbd) module=${module%.dbd} if [ "${modules[$module]}" = "" -a $module != base -a $module != slsMain -a $module != sls ] then echo "# file $dbd $module"; cat $dbd fi fi done for subs in $@ do debug includes: $INCLUDES > /dev/stderr echo "# file $subs" dbLoadTemplate -L $INCLUDES -- $subs done } | awk \ -v where=$WHERE \ -v require=$REQUIRE \ -v debug=$DEBUG \ -v epics=$EPICS \ -v overwrite=$OVERWRITE \ -v findrecords="$FIND" \ -v epicsmodules=${EPICS_MODULES:=/ioc/modules} \ ' BEGIN { epics3_14ver=gensub(/^3\.14\./,"",1,epics)+0 split(findrecords, find, " "); } {line ++} /# file / { filename = $3 module = $4 if (!module && match(filename,epicsmodules)==1) { /* happens for module templates only */ module = gensub(epicsmodules"/*([^/]*)/.*","\\1",1,filename) if (debug) { printf ("Using template %s requires \"%s\"\n", filename, module) > "/dev/stderr" } required[module] = 1 } line = 0 if (debug) { print "reading file " filename > "/dev/stderr" print "module=" module > "/dev/stderr" } next } /# line / { line = $3 -1 next } /^[ \t]*#/ { next } /\{/ {context++} /\}/ {context--} /choice[ \t]*([ \t]*.*[ \t]*,[ \t]*".*"[ \t]*)/ { if (valid) { match($0,/\([ \t]*(.*)[ \t]*,[ \t]*"(.*)"/,a) choice = a[2] if (ischoice[menu,choice]) { if (debug) { printf("%s:%d\n\tduplicate choice %s for menu %s\n\t%s\n", filename, line, choice, menu, definition[menu,choice]) > "/dev/stderr" } } else { ischoice[menu,choice] = 1 enum[menu,choicenum[menu]++] = choice choices[menu] = choices[menu] "\t\t\"" choice "\"\n" modulename[menu,choice] = module definition[menu,choice] = filename ":" line } } } /breaktable[ \t]*([ \t]*.*[ \t]*)/ { if (valid) { match($0,/\([ \t]*(.*)[ \t]*)/,a) choice = a[1] menu = "menuConvert" if (ischoice[menu,choice]) { if (debug) { printf("%s:%d\n\tduplicate choice %s for menu %s\n\t%s\n", filename, line, choice, menu, definition[menu,choice]) > "/dev/stderr" } } else { ischoice[menu,choice] = 1 enum[menu,choicenum[menu]++] = choice choices[menu] = choices[menu] "\""choice "\" " modulename[menu,choice] = module definition[menu,choice] = filename ":" line } } } /recordtype\(.*\)/ { match($0,/\((.*)\)/,a) rtype = a[1] if (definition[rtype]) { if (debug) { printf("%s:%d\n\tduplicate recordtype %s\n\t%s\n", filename, line, rtype, definition[rtype]) > "/dev/stderr" } if (modulename[rtype] !~ module) modulename[rtype] = modulename[rtype] "\" or \"" module valid = 0 } else { modulename[rtype] = module definition[rtype] = filename ":" line valid = 1 } } /field\(.*, *DBF_.*\)/ {if (!valid) next } /field\(.*, *DBF_.*\)/ { match($0,/\((.*),/,a) field = a[1] isfield[rtype,field] = 1 } /field\(.*, *DBF_NOACCESS\)/ { isnoaccess[rtype,field] = 1 next } /special\(SPC_DBADDR\)/ { isnoaccess[rtype,field] = 0 next } /field\(.*, *DBF_.*\)/ { if (field != "NAME") { fields[rtype] = fields[rtype] "\t\t" field "\n" } } /field\(.*, *DBF_.*LINK\)/ { islink[rtype,field] = 1 next } /field\(.*, *DBF_STRING\)/ { isstring[rtype,field] = 1 next } /field\(.*, *DBF_DEVICE\)/ { isdevice[rtype,field] = 1 next } /field\(.*, *DBF_FLOAT\)/ { isnumeric[rtype,field] = 1 next } /field\(.*, *DBF_DOUBLE\)/ { isnumeric[rtype,field] = 1 next } /field\(.*, *DBF_ULONG\)/ { isunsigned[rtype,field] = "ULONG" range[rtype,field] = 0xffffffff next } /field\(.*, *DBF_USHORT\)/ { isunsigned[rtype,field] = "USHORT" range[rtype,field] = 0xffff next } /field\(.*, *DBF_UCHAR\)/ { isunsigned[rtype,field] = "UCHAR" range[rtype,field] = 0xff next } /field\(.*, *DBF_LONG\)/ { isinteger[rtype,field] = "LONG" range[rtype,field] = 0x7fffffff next } /field\(.*, *DBF_SHORT\)/ { isinteger[rtype,field] = "SHORT" range[rtype,field] = 0x7fff next } /field\(.*, *DBF_CHAR\)/ { isinteger[rtype,field] = "CHAR" range[rtype,field] = 0x7f next } /field\(.*, *DBF_ENUM\)/ { isunsigned[rtype,field] = "ENUM" range[rtype,field] = 0xffff next } /menu\(.*\)/ { if (context == 1) { match($0,/\([ \t]*(.*)[ \t]*\)/,a) menu = a[1] if (choices[menu] != "") { #printf("menu %s redefined\n", menu) > "/dev/stderr" valid = 0 } else { valid = 1 choicenum[menu] = 0 definition[menu] = filename ":" line } } else { match($0,/\((.*)\)/,a) menu = a[1] menuname[rtype,field] = menu } } /size\(.*\)/ { if (valid) { match($0,/\((.*)\)/,a) stringsize[rtype,field] = a[1]-1 if (field == "NAME" && !maxNameLen) { maxNameLen = stringsize[rtype,field] } } } /device[ \t]*\(.*\)/ { match($0,/\([ \t]*(.*)[ \t]*,[ \t]*(.*)[ \t]*,.*,[ \t]*"(.*)"[ \t]*\)/,a) rtype = a[1] ltype = a[2] dtype = a[3] if (ltype == "CONSTANT") { issoft[rtype,dtype] = 1 } if (definition[rtype, dtype]) { if (debug) { printf("%s:%d\n\tduplicate device type \"%s\" for record type %s\n\t%s\n", filename, line, dtype, rtype, definition[rtype, dtype]) > "/dev/stderr" } if (modulename[rtype] !~ module) modulename[rtype, dtype] = modulename[rtype, dtype] "\" or \"" module } else { modulename[rtype, dtype] = module devices[rtype] = devices[rtype] "\t\t\"" dtype "\"\n" definition[rtype, dtype] = filename ":" line } } /record\(.*,".*"\)/ { match($0,/\((.*),"(.*)"/,a) rtype = a[1] record = a[2] if (recordtype[record] != "" && recordtype[record] != rtype) { printf("%s:%d\n\trecord \"%s\" redefined with different type %s\n\tfirst defined as %s in %s\n", filename, line, record, rtype, recordtype[record], definition[record]) > "/dev/stderr" } else { recordtype[record] = rtype definition[record] = filename ":" line if (modulename[rtype] && !required[modulename[rtype]]) { if (debug) { printf ("recordtype %s requires \"%s\"\n", rtype, modulename[rtype]) > "/dev/stderr" } required[modulename[rtype]] = 1 } } if (length(record)>maxNameLen && record !~ /\$\(.*\)/) { printf("%s:%d\n\trecord name \"%s\" %d chars too long (max %d)\n\tmaybe this works with a higher EPICS version (this is %s)\n", filename, line, record, length(record)-maxNameLen, maxNameLen, epics) > "/dev/stderr" } if (record ~ /[ \t.]/) { printf("%s:%d\n\trecord \"%s\"\n\twhitespaces or dots are not allowed in record names\n", filename, line, record) > "/dev/stderr" } soft = 1 } /alias\(".*\",".*\")/ { if (epics3_14ver < 11) { printf ("%s:%d\n\t%s\n\trequires EPICS version >= 3.14.11 (this is %s)\n", filename, line, $0, epics) > "/dev/stderr" } match($0,/\("(.*)","(.*)"/,a) record = a[1] aliasname = a[2] if (recordtype[record] == "") { printf ("%s:%d\n\talias \"%s\" undefined record \"%s\"\n", filename, line, aliasname, record) > "/dev/stderr" } alias[aliasname] = record; } /field\(.*, ".*\")/ { if (!stringsize[rtype,"NAME"]) next; match($0,/\((.*), "(([^-+0-9\.][^\. ]*)?.*)"\)/,a) field = a[1] value = a[2] menu = menuname[rtype,field] if (fieldvalue[record,field] && fieldvalue[record,field] != value && !overwrite) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tWarning: value redefined from \"%s\" to \"%s\"\n\tfirst defined in %s\n", filename, line, rtype, record, field, fieldvalue[record,field], value, definition[record,field]) > "/dev/stderr" } fieldvalue[record,field] = value definition[record,field] = filename ":" line if (value == "") next knownproblem=known[filename,line,rtype,field,value] known[filename,line,rtype,field,value]=1 if (!isfield[rtype,field] && !knownproblem) { printf("%s:%d\n\t%s record \"%s\"\n\tUnknown field %s. Known fields:\n%s", filename, line, rtype, record, field, fields[rtype]) > "/dev/stderr" } else if (isnoaccess[rtype,field] && !knownproblem) { printf("writing \"%s\" to NOACCESS field %s.%s\n", value, record, field) > "/dev/stderr" } else if (islink[rtype,field]) { if (value !~ /^ *(\$\([^)]*\) *)*$/ && \ tolower(value) !~ /^ *[+-]?(0[0-7]*|0x[0-9a-f]+|[1-9][0-9]*|([0-9]+.?[0-9]*|[0-9]*.?[0-9]+)(e[-+]?[0-9]+)?|inf|nan) *$/ && \ ((field != "INP" && field != "OUT") || soft)) { reference[gensub(/ .*/,"",1,value)] = filename ":" line "\n\t" record "." field } if (field == "INP" || field == "OUT") { iolink[record] = field } } else if (value ~ /\$\(.*\)/) { } else if (isdevice[rtype,field]) { if (iolink[record]) { if (!dtyp[record] && !knownproblem) { printf("%s defined after %s in record %s\n", field, iolink[record], record) > "/dev/stderr" } } if (!definition[rtype,value] && !knownproblem) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tUnknown device type \"%s\". Known types:\n%s", filename, line, rtype, record, field, value, devices[rtype]) > "/dev/stderr" } else { if (modulename[rtype,value] && !required[modulename[rtype,value]]) { if (debug && !knownproblem) { printf ("%s \"%s\" for record type %s requires \"%s\"\n", field, value, rtype, modulename[rtype,value]) > "/dev/stderr" } required[modulename[rtype,value]] = 1 } } soft = issoft[rtype,value] dtyp[record] = value } else if (knownproblem) { next } else if (menu != "") { if (!ischoice[menu,value]) { if (value != int(value) || value < 0) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tUnknown %s choice \"%s\". Known choices:\n%s", filename, line, rtype, record, field, menu, value, choices[menu]) > "/dev/stderr" } else { if (enum[menu,value] != "") printf("%s:%d\n\t%s record \"%s\" field %s\n\tbetter use %s choice \"%s\" instead of number \"%s\"\n", filename, line, rtype, record, field, menu, enum[menu,value], value) > "/dev/stderr" } } } else if (isnumeric[rtype,field]) { if (!match(tolower(value),/^ *[+-]?(0[0-7]*|0x[0-9a-f]+|[1-9][0-9]*|([0-9]+.?[0-9]*|[0-9]*.?[0-9]+)(e[-+]?[0-9]+)?|inf|nan) *$/)) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tvalue \"%s\" should be numeric\n", filename, line, rtype, record, field, value) > "/dev/stderr" } } else if (isinteger[rtype,field]) { if (!match(tolower(value),/^ *[+-]?(0[0-7]*|0x[0-9a-f]+|[1-9][0-9]*) *$/)) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tvalue \"%s\" should be %s (%d ... %d)\n", filename, line, rtype, record, field, value, isinteger[rtype,field], -range[rtype,field]-1, range[rtype,field]) > "/dev/stderr" } if (value+0 > range[rtype,field] || value+1 < -range[rtype,field]) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tvalue %s is out of %s range %d ... %d\n", filename, line, rtype, record, field, value, isinteger[rtype,field], -range[rtype,field]-1, range[rtype,field]) > "/dev/stderr" } } else if (isunsigned[rtype,field]) { if (!match(tolower(value),/^ *+?(0[0-7]*|0x[0-9a-f]+|[1-9][0-9]*) *$/)) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tvalue \"%s\" should be %s (0 ... %d)\n", filename, line, rtype, record, field, value, isunsigned[rtype,field], range[rtype,field]) > "/dev/stderr" } if (value+0 > range[rtype,field]) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tvalue %s is out of %s range 0 ... %d\n", filename, line, rtype, record, field, value, isunsigned[rtype,field], range[rtype,field]) > "/dev/stderr" } } else if (isstring[rtype,field]) { if (length(value)>stringsize[rtype,field]) { printf("%s:%d\n\t%s record \"%s\" field %s\n\tstring \"%s\" is %d chars too long (max %d)\n", filename, line, rtype, record, field, value, length(value)-stringsize[rtype,field], stringsize[rtype,field]) > "/dev/stderr" } } } END { for (target in reference) { split(target,a,".") record = a[1] field = a[2] if (alias[record] != "") { record = alias[record] } for (r in find) { if (record ~ find[r]) {print reference[target] " -> " target} } if (recordtype[record] == "") { if (where) print reference[target] " -> " target else print target } else { if (field == "") { field = "VAL" } rtype = recordtype[record] split (reference[target],a," ") link = a[2] if (!isfield[rtype,field]) { if (!definition[rtype]) printf("%s:%d\n\tLink \"%s\"\n\tpoints to unknown record type %s.\n", filename, line, link, rtype) else printf("%s:%d\n\tLink \"%s\"\n\tpoints to non-existing %s field \"%s.%s\". I only know:\n%s", filename, line, link, rtype, record, field, fields[rtype]) > "/dev/stderr" } else if (isnoaccess[rtype,field]) { if (!(field == "TIME" && gensub(/.*\./,"",1,link) == "TSEL")) { printf("%s:%d\n\tLink \"%s\" points to NOACCESS %s field \"%s.%s\"\n", filename, line, link, rtype, record, field) > "/dev/stderr" } } } } if (require) { for (module in required) { printf ("require \"%s\"\n", module) } } } '