Cover V11, I12

Article
Listing 1
Listing 2

dec2002.tar

Listing 2 Triplet (SX.AWK, CX.AWK, C2X.AWK) for implementing a Remote Execution Capability. NETGZ: Sent by CP.AWK via C2X.AWK and invoked by SX.AWK, a sample script that waits for and untars a directory tree.

NETGZ -----------------------------------------------------------------

#!/bin/bash

. $SCR/bash.i

argc $# 4 "w.netgz ^y.hostid ^m.xfrlog ^tarball rpar rdir"

assert file $3

#----------------------------------------
gethost $1
hostid=$RET
xfrlog=$2
localgz=$3
tball=${3##*/}
bn=${tball%.*}
parentdir=$4
newdir=$5

if [ "${tball: -3:3}" = "tar" ]; then
  decomp="xpf"
else
  decomp="zxpf"
fi

# To avoid collisions, scripts must have unique names.
# Embed this script's process id. Not foolproof, but reasonable.

myhost=$(hostname)
remotegz=$parentdir/$tball
log=/var/log/netcp-$bn-$$
done=/var/run/$tball.DONE
localscr=$SCR/netcp-$bn-$$.bash
remotescr=/var/run/netcp-$bn-$$.bash
ini=$SCR/netcp-$bn-$$.ini
pid=/var/run/netcp-$bn-$$.pid
#----------------------------------------

# Get the size in bytes of the file being transferred
a=($(ls -l $localgz))
let size=${a[4]}
# Because awk began life as a text-processing tool, there are
# yet problems with final newlines.
let size--
# Calculate a waiting period based on the size of the file.
# This number was derived empirically.
let incr=25000000
if [ $size -lt $incr ]; then
  let secs=5
else
  secs=$((size/incr))
fi

cat <<EOF > $localscr
echo \$$ > $pid
# Wait for the tarball to be completely transferred.
let size=0
while [ \$size -lt $size ]; do
  sleep $secs
  a=(\$(ls -l $parentdir/$tball))
  let size=\${a[4]}
done
# Backup the remote directory, if it already exists.
cd $parentdir
if [ -d $newdir ]; then
  n=1
  while [ -d $newdir.\$n ]; do let n=\$n+1; done
  mv $newdir $newdir.\$n
fi
# Untar the tarball, creating a new directory tree.
tar $decomp $tball
rm $tball
rm $pid
rm $remotescr

# FreeBSD cleanup
chmod og-w $newdir 2> /dev/null

# This script signals completion to the remote client by sending back to 
# /var/run on the remote host a file with the tarball's basename + ".DONE" 

echo DONE > $done
cp.awk $myhost u+rw t $done
EOF

cat <<EOF > $ini
WAIT 2
LOG $log
EXE s $localscr $remotescr
AUX b u+rw $localgz $remotegz
EOF

echo ""
cat $ini
echo ""

x "c2x $hostid $ini" -c

C2X.AWK --------------------------------------------------------------

#!/bin/awk -f
# This script invokes CX.AWK
# Usage: c2x.awk ip-addr inifile
#
# The ini file contains a single instance each of 3 mandatory keywords:
#
# - WAIT ->
#        arg 1: the number of seconds to wait between copies.
# - EXE  ->
#        arg 1: flag, ?/b for a script or binary executable
#        arg 2: name of local script/binary to transmit to the remote host
#        arg 3: complete pathname of script/binary to create on the remote 
#               host and then execute
# - LOG  ->
#        arg 1: local file into which to copy output from the remote executable
#
# The ini file contains multiple instances of these keywords:
#
# - AUX  ->
#        arg 1: type of file ?/b for text or binary
#        arg 2: permissions string for the copied file
#        arg 3: local auxiliary file needed by the executable named by EXE
#        arg 4: complete pathname of auxiliary file to create on the 
#               remote host

function parseini (ini) {
  i=2
  loc[1]=loc[2]="*"
  rmt[1]=rmt[2]="*"
  prm[1]=prm[2]="*"
  typ[1]=typ[2]="*"
  wait = -1
  while ((getline < ini) > 0) {
    if ($1=="WAIT") {
      wait=$2
      continue
    }
    if ($1=="LOG")  {
      if (NF < 2) {
        print (ini ": LOG keyword requires arg")
        return 0
      }
      loc[1] = $2
      continue
    }
    if ($1=="EXE") {
      if (NF < 4) {
        print (ini ": EXE keyword requires 3 args")
        return 0
      }
      if ($2=="b")
        prm[2]="u+x"
      else
        prm[2]="u+rw"
      typ[2] = $2
      loc[2] = $3
      rmt[2] = $4
      continue
    }
    if ($1=="AUX") {
      if (NF < 5) {
        print (ini ": AUX keyword requires 4 args")
        return 0
      }
      ++i
      typ[i] = $2
      prm[i] = $3
      loc[i] = $4
      rmt[i] = $5
      continue
    }
  }
  if (wait==-1) {
    print ("No WAIT keyword in " ini)
    return 0
  }
  if (loc[1]=="*") {
    print ("No LOG keyword in " ini)
    return 0
  }
  if (loc[2]=="*") {
    print ("No EXE keyword in " ini)
    return 0
  }
  filecnt = i
  return 1
}

function dump() {
  for (i=1; i<=filecnt; ++i) {
    print ("wait\t" wait)
    print ("loc[" i "]\t" loc[i])
    print ("rmt[" i "]\t" rmt[i])
    print ("prm[" i "]\t" prm[i])
    print ("typ[" i "]\t" typ[i])
  }
}

BEGIN {
  if (ARGC<3) {
    print "c2x.awk  remotehost  inifile"
    print "ex: c2x.awk  galleon  c2x.ini"
#              0        1        2
  } else {
    if (!parseini(ARGV[2])) exit 0
    for (i=2; i<=filecnt; ++i) {
      sys = sprintf ("cp.awk %s %s %s %s \
                     %s",ARGV[1],prm[i],typ[i],loc[i],rmt[i])
      print sys
      system (sys)
      if (wait) system ("sleep " wait)
    }
    sys = sprintf ("cx.awk %s %s %s %s",ARGV[1],typ[2],rmt[2],loc[1])
    print sys
    system (sys)
    system ("date >> /var/log/c2x")
    l = sprintf("/cmn/scr/c2x.awk %s %s",ARGV[1],ARGV[2])
    print l >> "/var/log/c2x"
  }
}


CX.AWK -----------------------------------------------------------------

#!/bin/awk -f
# cx.awk remote-host ftype remote-executable [local-output-file]
#
# This client that transmits one item:
# 1. Two fields separated by space:
#    - ?/b for script or binary executable
#    - The name of the script/binary on the remote host.
#
# This client immediately receives back from the remote server one item:
# 1. Output from the executable.
#
# This client pairs with server SX.AWK

BEGIN {
  if (ARGC<5) {
    print "cx.awk remote-host ?/b remote-executable [local-output-file]"
    print "cx.awk  galleon     s  /root/scr/netscr  /var/log/netscr.log"
#            0        1        2          3                  4
  } else {
    getline < "/var/run/sx.port"
    if (NF==0) {
      print "No file: /var/run/sx.port"
      exit 1
    }
    Net = ("/inet/tcp/0/" ARGV[1] "/" $1)
    if (ARGC<5) ARGV[4]="/var/log/cx.outp"
    print (ARGV[2] " " ARGV[3]) |& Net
    while ((Net |& getline rec) > 0) print rec > ARGV[4]
    close (Net)
    system ("date >> /var/log/cx")
    l = sprintf("/cmn/scr/cx.awk %s %s %s %s",ARGV[1],ARGV[2],ARGV[3],ARGV[4])
    print l >> "/var/log/cx"
  }
}

SX.AWK -----------------------------------------------------------------

#!/bin/awk -f
# sx.awk PORT_NUMBER &
#
# This server receives the following item from the client:
# 1. Two fields separated by space:
#    - A flag, ?/b, indicating whether the executable is script or binary
#    - The name of an executable on the remote host to execute.
#
# The server passes nothing back. It is expected that, if completion
# should be signaled back to the remote client, the executable
# will perform that function. The sender knows its own host name, easing 
# notification.
#
# This server pairs with client CX.AWK.

BEGIN {
  system ("echo " ARGV[1] " > /var/run/sx.port")
  Net = ("/inet/tcp/" ARGV[1] "/localhost/0")
  Net |& getline
  if (NF < 2) {
    msg = sprintf("sx.awk: \"%s\"\nsx.awk ?/b executable",$0)
    print msg
  } else {     
    ftype = $1
    exe = $2
    if (ftype=="b" ) {
#     Execute the binary and send back its stdout output.
      while ((exe | getline) > 0) print $0 |& Net 
    } else {
#     By convention, the shell to use is passed as the final dot-extension.
      cnt = split (exe,arr,".")
      if (arr[cnt]=="bash") {
        sys = ("bash < " exe " &")
      } else if (arr[cnt]=="sh") {
        sys = ("sh < " exe " &")
      } else {
        sys = sprintf ("echo \"sx.awk: unknown shell: %s\"",arr[cnt])
      }
      system (sys)
    }
  }
  close (Net)
  system ("/cmn/scr/sx.awk " ARGV[1] " &")
  system ("date >> /var/log/sx")
  l = sprintf ("/cmn/scr/sx.awk %d %s %s",ARGV[1],ftype,exe)
  print l >> "/var/log/sx"
}