# $EPIC: exec,v 1.5 2003/04/19 00:36:02 crazyed Exp $
Synopsis:
   $exec(<program> <args>)

Technical:
   * If the <program> argument is ommited, the empty string is returned.
   * Otherwise, <program> is forked and executed with <args> for arguments.
   * This function returns 3 (three) FDs, which are pipes to/from STDIN,
     STDOUT and STDERR of the forked process respectively.
   * The first FD returned (STDIN) is a write mode FD.
   * The subsequent FDs (STDOUT and STDERR) are read mode FDs.
   * The FDs returned can be used with $read(), $write(), etc, however...
   * Writing to a pipe that is full will cause epic to block.
   * Reading from a pipe that is empty will cause epic to block.
   * When epic is blocked waiting for a <program> which is also blocked
     waiting for epic, deadlock will occur.  This is where the danger of
     this function lies.
   * The write pipes will remain full while <program> is not emptying them by
     reading or closing them.
   * The read pipes will remain empty while <program> is not filling them by
     writing or closing them.
   * The only guaranteed way to prevent deadlock is to examine the way the
     program reads and writes its input/output and write your script to avoid
     the traps listed above.
   * This function is relatively new.  The /exec command may be better
     suited to your needs given the semantic difficulty of coding with
     this function.
   * Unlike the /exec command, <program> will not be running under a shell.
     To emulate this feature, use $exec(sh -c "<program> <args>").

Practical:
   This function offers an easier interface and a finer grain control
   over the process than /exec, but comes at the expense of being more
   dangerous to use.

   You could use this function to implement real time text filtering or
   scrambling using the unix tool "sed" perhaps.

   $exec() might be a better alternative to $perl() and $tcl() for people
   who wish to distribute their scripts and need a certain level of
   compatibility that the embedded languages don't provide.

Returns:
   One writable FD and two readable FDs.

Examples:
   #
   # An example of what we can expect.
   #
   fe ($exec(tr a-zA-Z A-Za-z)) fd {
       echo $write($fd qwerASDF):$read($fd):$close($fd)
   }
   #
   # Output
   #
   Out1: 9::0           # STDIN wrote 9 bytes (including NL), read failed,
   Out2: -1:QWERasdf:0  # STDOUT write failed, read 9 bytes,
   Out3: -1::0          # STDERR write failed, read nothing,
                        #  FD close succeeded in all cases.

   #
   # Fortunately for us, md5sums i/o is very predictable.  It, like
   # some other programs uses the EOF of its input as a signal to start
   # delivering output, so it is always safe for $exec() so long as we
   # do not attempt to read its output before closing its input.
   #
   # Note that the technique we use here can be used for most programs
   # and all filters if we restrain ourselves from writing too much data
   # to STDIN. Exactly how much data can be written depends on the "pipe
   # size" ulimit which can be changed with the ulimit command in any(?)
   # bourne compatible shell.
   #
   # Also note that in these examples, the FDs that aren't used are
   # closed before processing begins rather than after.  It is
   # important to do this because if the program tries to read/write
   # a closed FD, it will always return immediately rather than
   # blocking.
   #
   alias md5 {
	# a fast way to set three local variables.
	fe ($exec(md5sum)) in out err {break}
	@ close($err)
	@ write($in $*)
	@ close($in)
	@ function_return = read($out)
	@ close($out)
   }

   #
   # Translate upper to lower and lower to upper using a perl script.
   # The critical thing about this alias is the $|=1 in the perl script,
   # which turns autoflush on so that we receive the output immediately
   # after feeding it the input.  The gnu tr command cannot be made to
   # do this, which makes it unsuitable for this application.
   #
   # Anyway, the reason this is critical is that we're going to $exec()
   # the perl script outside of the alias, perhaps at bootup, and leave
   # it running in the background forever with the assumption that every
   # line we give it is going to give us precisely one line back.  Of
   # course, this is a very delicate thing since errors are going to
   # accumulate.
   #
   # Also, note the double quotes where we might normally use single
   # quotes in the shell.  The input is broken into arguments according
   # to epics "word" rules.  There is no equivalent to the single quotes
   # under these rules, though you can escape "quoting hell" by wrapping
   # the relevant parts of your script in braces like I have for this
   # example.
   #
   fe ($exec(perl -lpe "BEGIN{$|=1};{tr/a-zA-Z/A-Za-z/}")) in out err {
	@ ::transcase.in = in
	@ ::transcase.out = out
	@ close($err)
	break
   }
   alias transcase {
	@ write($transcase.in $*)
	return $read($transcase.out)
   }

See Also:
   exec(5); read(6); write(6); close(6);

