morgan@ms.uky.edu (Wes Morgan) (06/27/91)
Occasionally, I find that I need to know the path between two BITNET nodes. I developed the tools included below to per- form this task. Both the REXX and Unix versions of this program require the BITNET LINKS file, which is available from LISTSERV@BITNIC. I have tested these programs using the most recent copy of BITNET LINKS; if you've made any local changes to BITNET LINKS (or its format), you're on your own. I can't take full credit for the REXX version; it was sent to me (in a very rough form) several years ago. I merely cleaned up the code, added the descriptive output, and added comments. The Unix script, however, is all mine. The REXX routine was tested under CMS Rel 5. The Unix script was tested under AT&T System V Release 3.2.3. If you're run- ning another operating system, you may need to alter the code. I only request that you clearly mark your changes before passing the code to other users; I don't want to receive questions about code I didn't write! 8) Comments/questions/suggestions/flames can be directed to me at any of the email addresses at the end of this posting. Having said all that, here's the code: ------------------begin REXX code------------------------ /************************************************/ /* NODE EXEC - find and list path between nodes */ /* */ /* Syntax NODE node1 node2 */ /* or NODE node2 <home node is default> */ /* */ /* Original Author : Unknown */ /* Version 2 by: Wes Morgan */ /* morgan@engr.uky.edu */ /* */ /* Summary of revisions */ /* Date by Comments */ /* ???? Unknown Original code. Several */ /* versions on BITNET. */ /* 8804 WM Cleaned up code. Added */ /* comments. */ /* 8805 WM Added code to display */ /* description of sites */ /* for those people who */ /* don't know node names */ /* by heart. */ /* */ /* Requires: BITNET LINKS file, available from */ /* LISTSERV@BITNIC */ /************************************************/ /* General setup. The user's home node is extracted from IDENTIFY. */ /* Two arguments are expected: a start node and a destination node. */ /* If no start node is given, the default is the user's home node. */ /* Failure to give any arguments results in a help message. */ 'identify (stack' parse pull . . localnode . parse arg from to . upper from to if from = '' then do say 'Syntax : NODE fromnode tonode' say ' or : NODE tonode' exit 1 end if to = '' then do to = from from = localnode end /* Finding the routing path between the two nodes. The file */ /* BITNET LINKS is read completely onto the stack. After get- */ /* ting past the file header, the information for each node */ /* is placed in an array indexed by node name. For instance, */ /* the array element bitnet.UKCC contains the value "UKCCB", */ /* which is UKCC's connection to the net at large. This loop */ /* continues as long as there are lines on the stack. */ /* */ /* The array description. contains the site description. */ say 'Finding path from 'from' to 'to say 'execio * diskr bitnet links * (finis' beforestart = 1 bitnet. = '' description. = '' do queued() parse pull 6 node ' ' 15 root ' ' 25 sitename 62 . if root = 'ROOT' then beforestart = 0 if beforestart then iterate bitnet.node = root description.node = sitename end /* Finding the path from the start node to the destination node. */ /* The first element of the uplist structure is the start node. */ /* The home node's connection node, as stored in the array cre- */ /* ated above, is added to the uplist structure. The array is */ /* then scanned for the next connection. This process continues */ /* until we get to the "ROOT" entry, which means we've gone as */ /* far as we can. Notice that this approach will rarely find the */ /* complete path. That's fine; we don't want the complete path */ /* yet. We want the path back to ROOT, which is CUNYVM. After */ /* we find the path to CUNYVM for the destination node, we will */ /* simply find the two paths' common ground and go from there. */ uppoint = 0 /* Initialize counter */ uplist.0 = from /* First element of uplist is start node */ updesc.0 = description.from do while uplist.uppoint ^= 'ROOT' /* As long as connections exist */ lookup = uplist.uppoint /* Index by node name */ updesc.uppoint = description.lookup /* Get the site description */ uppoint = uppoint + 1 /* Increment counter */ uplist.uppoint = bitnet.lookup /* Next node = connection of last */ if uplist.uppoint = '' /* If null, exit */ then do say 'No entry for 'lookup' found' exit 999 end end /* Find path back from destination node to CUNYVM (root). */ /* This is accomplished in the same manner as the uplist */ /* created above. */ dnpoint = 0 dnlist.0 = to dndesc.dnpoint = description.to do while dnlist.dnpoint ^= 'ROOT' lookup = dnlist.dnpoint dndesc.dnpoint = description.lookup dnpoint = dnpoint + 1 dnlist.dnpoint = bitnet.lookup if dnlist.dnpoint = '' then do say 'No entry for 'lookup' found' exit 999 end end /* Find the common ground between the two lists. Basically, */ /* we are finding the halfway point, i.e. that point at which */ /* the uplist and downlist merge. As the uplist is printed, */ /* the downlist is scanned for a match. Once a match is found */ /* in the downlist, the uplist is dropped, and the downlist */ /* "takes up where we left off", working back to our goal. */ count = 0 /* Initialize node count */ do loop = 0 to uppoint /* Do as long as elements remain in uplist */ prnt = justify(uplist.loop,12) /* Justify for nice printing */ say prnt updesc.loop /* Output the information */ count = count + 1 /* Increment node count */ do search = 0 to dnpoint /* Search all elements of dnlist */ match = 0 /* Set match flag low */ if uplist.loop = dnlist.search /* If we find a match, */ then do match = 1 /* set the flag high, */ dnpoint = search -1 /* set startpoint for dnlist, */ leave search /* and get out of here */ end end if match then leave loop /* If match flag high, quit */ end do loop = dnpoint to 0 by -1 /* Working back from match point, */ prnt = justify(dnlist.loop,12) /* Justify for nice output */ say prnt dndesc.loop /* Output site and description */ count = count + 1 /* and increment node count */ end say say count - 1' nodes away' /* Print node count */ exit count -1 /* Returned value is node count */ return /* That's all, folks! */ --------------End REXX code---------------- And now, the Unix shell script: --------------Begin Unix shell script----------------- #!/bin/sh # # bitpath - Determine path between two BITNET nodes # # Written by Wes Morgan, morgan@engr.uky.edu, 27 Jun 91 # # This shell script requires the BITNET LINKS file, which is available # from LISTSERV@BITNIC (aka listserv@bitnic.bitnet). # # Syntax: bitpath [-f] [site1] site2 # If -f is specified, a "full" listing is output, containing each # site's description and ISO country code. # If "site1" is specified, it will be used as the origin for the # search. If omitted, the site named in DEFAULT, below, # will be used as the origin. # "site2" is the destination node for the search. # # This shell script has been tested under AT&T System V Release 3.2.3 # # Known problems: # Some entries contain descriptions which "run into" the ISO # country code. If this happens, you will see an extra field # (the "software" field) displayed for that site. # # LINKFILE should be set to the name of your copy of BITNET LINKS. LINKFILE=bitlinks # DEFAULT should be set to the default origin node for searches. # This nodename MUST be in CAPTIAL LETTERS!! # I use UKCC, which is the University of Kentucky Computing Center. DEFAULT=UKCC # # Parse the command line. # errflag=0 full=0 if [ -z "$1" ] then errflag=1 fi if [ "$1" = "-f" -o "$1" = "-F" ] then full=1 shift fi if [ -z "$1" ] then errflag=1 else if [ -z "$2" ] then upnode=$DEFAULT uplist=$DEFAULT dnnode=`echo $1 | tr "[a-z]" "[A-Z]"` dnlist=$dnnode else upnode=`echo $1 | tr "[a-z]" "[A-Z]"` uplist=$upnode dnnode=`echo $2 | tr "[a-z]" "[A-Z]"` dnlist=$dnnode fi fi if [ $errflag -eq 1 ] then echo Syntax: bitpath [-f] [site1] site2 exit 1 fi echo # # BITNET is a tree-based network, originating from the City University # of New York (CUNYVM). Therefore, our initial task is to determine the # path from the origin node back to the "root" at CUNYVM. # # The format of BITNET LINKS is as follows: # 0693 UKCC UGA U Kentucky Comp Ctr ,US RSCS 91/03/31 # # From right to left, the fields are: # - BITNET node number (unused in this script) # - Site name # - The next site up the network tree # - Description of the site # - ISO country code # - Network software used for BITNET (unused in this script) # - Last update of this site's entry (unused in this script) # # At this stage, we are only interested in the second and third fields. # We will construct a list of sites from the origin to CUNYVM. Since # CUNYVM is the root of the BITNET tree, it's "next site" entry is "ROOT"; # we use that entry to end the search loop. updone=0 while [ $updone -eq 0 ] do line=`grep $upnode $LINKFILE | awk '$2 ~ /^'"$upnode"'$/'` if [ -z "$line" ] then echo "No entry for $upnode found." exit 1 fi next=`echo $line | awk '{ print $3 }'` if [ "$next" = "ROOT" ] then updone=1 else upnode=$next uplist=`echo $uplist $next` fi done # # Construct a path from the destination node to CUNYVM (the root). # The algorithm is identical; the only change is that we construct # the list in reverse order. # dndone=0 while [ $dndone -eq 0 ] do line=`grep $dnnode $LINKFILE | awk '$2 ~ /^'"$dnnode"'$/'` if [ -z "$line" ] then echo "No entry for $dnnode found." exit 1 fi next=`echo $line | awk '{ print $3 }'` if [ "$next" = "ROOT" ] then dndone=1 else dnnode=$next dnlist=`echo $next $dnlist` fi done # # Now that we have our two paths, we need to find the "common ground" # between them. We move through the list from left to right. At each # step, we scan ahead, looking for a duplicate entry. If we find one, # that site is the "common ground"; we skip over to the duplicate and # finish the path from that point. # # For instance, the path from UKCC (Kentucky) through CUNYVM (the root) to # SUVM (Syracuse) is generated as: # UKCC UGA CUNYVMV2 CUNYVM CUNYVM CUNYVMV2 CORNELLC SUVM # We don't need the redundant "CUNVMV2 CUNYVM CUNYVM CUNYVMV2" portion of # this path, because actual network traffic will not reach CUNYVM; it will # be routed from CUMYVMV2 to CORNELLC. Therefore, in this example, CUNYVMV2 # is the "common ground" for which this section of code searches. # cleanlist=`echo "$uplist $dnlist" | awk ' { for(x=1;x<=NF;x++) { print $x; for(y=x+1;y<=NF;y++) { if($y == $x) { x = y; break; } } } }'` # # Output results. If the -f option is used, the listing includes # site description and country code; if not, only the node names # are printed, separated with "->". # if [ $full -eq 1 ] then echo $cleanlist | awk '{ for(x=1;x<=NF;x++) printf("%s\n",$x); }' | while read target do line=`grep $target $LINKFILE | awk '$2 ~ /^'"$target"'$/'` descrip=`echo $line | awk '{ for(x=4;x<NF;x++) { printf("%s ",$x); if (index($x,",") == 1) x=NF; } } END { printf("\n"); }'` justify=`echo $target | awk '{ printf("%-8s",$1); }'` echo "$justify\t$descrip" done echo else echo $cleanlist | sed "s/ /->/g" fi exit 0 ------------------end Unix shell script---------------------- -- morgan@ms.uky.edu |Wes Morgan, not speaking for| ....!ukma!ukecc!morgan morgan@engr.uky.edu |the University of Kentucky's| morgan%engr.uky.edu@UKCC morgan@ie.pa.uky.edu |Engineering Computing Center| morgan@wuarchive.wustl.edu