--
--	proclib.occ - occam process library
--	Copyright (C) 2000 Fred Barnes <frmb2@ukc.ac.uk>
--
--	This program is free software; you can redistribute it and/or modify
--	it under the terms of the GNU General Public License as published by
--	the Free Software Foundation; either version 2 of the License, or
--	(at your option) any later version.
--
--	This program is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--	GNU General Public License for more details.
--
--	You should have received a copy of the GNU General Public License
--	along with this program; if not, write to the Free Software
--	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--

--{{{  module documentation
--** Process library.
--
-- The process library provides facilities for running other OS programs and
-- manipulating the environment.
--
-- Some [@code PROC]s are also provided in [@code ALT]able versions; for more
-- information on how to use these, see the [@text sock] module
-- documentation.
--
-- To use this library:
--
-- [@code
-- #INCLUDE "proc.module"]
--
-- @module proc

--}}}

#INCLUDE "proclib.inc"
#INCLUDE "filelib.inc"
#USE "file.lib"


--{{{  externals
#PRAGMA EXTERNAL "PROC B.pl.run (VAL []ENVIRONMENT env, VAL [][]BYTE command, VAL INT kyb.fd, scr.fd, err.fd, INT result) = 2"
#PRAGMA EXTERNAL "PROC C.pl.os.getenv (ENVIRONMENT env, VAL []BYTE ident) = 0"
#PRAGMA EXTERNAL "PROC C.pl.os.setenv (VAL []ENVIRONMENT env) = 0"
#IF DEFINED (OCCBUILD.KROC)
#PRAGMA EXTERNAL "PROC C.killcall (CHAN OF INT c, INT result) = 0"
#ENDIF
--}}}  

--{{{  PROC proc.run (VAL []ENVIRONMENT env, VAL [][]BYTE command, VAL INT kyb.fd, scr.fd, err.fd, INT result)
--* Run an external program.
--
-- The [@code command] array specifies the program name (the first element) and
-- arguments (the remaining elements). The child process is executed using
-- [@text execvp], so the system path will be searched if the program name is
-- not an absolute path to an executable. Elements of [@code command] must be
-- padded with [@code '*0'] characters.
--
-- If any of the file descriptors are specified as [@code -1], that descriptor
-- will be closed in the child process. Since many programs will behave
-- unpredictably if the standard file descriptors are closed when they are
-- invoked, making use of this feature is not recommended.
--
-- @param env Additional environment variables
-- @param command The process name and arguments
-- @param kyb.fd File descriptor to connect to the child process's standard
--   input
-- @param scr.fd File descriptor to connect to the child process's standard
--   output
-- @param err.fd File descriptor to connect to the child process's standard
--   error
-- @param result [@code -1] if an error occurred, otherwise the exit status of
--   the process
PROC proc.run (VAL []ENVIRONMENT env, VAL [][]BYTE command, VAL INT kyb.fd, scr.fd, err.fd, INT result)
  SEQ
    B.pl.run (env, command, kyb.fd, scr.fd, err.fd, result)
:
--}}}  

#IF DEFINED (OCCBUILD.KROC)
--{{{  PROC proc.altable.run (CHAN OF BOOL kill, CHAN OF INT response, VAL []ENVIRONMENT env, VAL [][]BYTE command, VAL INT kyb.fd, scr.fd, err.fd, INT result)
--* [@code ALT]able version of [@ref proc.run].
PROC proc.altable.run (CHAN OF BOOL kill, CHAN OF INT response, VAL []ENVIRONMENT env, VAL [][]BYTE command, VAL INT kyb.fd, scr.fd, err.fd, INT result)
  CHAN OF INT c, signal:
  PAR
    --{{{  make blocking call
    INT tres:
    SEQ
      BX.pl.run2 (c, env, command, kyb.fd, scr.fd, err.fd, tres)
      signal ! tres
    --}}}  
    --{{{  listen for terminations
    PRI ALT
      --{{{  result comming in
      signal ? result
        PAR
          BOOL any:
          kill ? any
          response ! (-2)
      --}}}  
      --{{{  kill signal
      BOOL any:
      kill ? any
        INT l.result:
        SEQ
          C.killcall (c, l.result)
          signal ? result
          response ! l.result
      --}}}  
    --}}}  
:
--}}}  
#ENDIF

--{{{  PROC proc.wrapper (VAL []ENVIRONMENT env, VAL [][]BYTE command, CHAN OF BYTE kyb.in, scr.out, err.out, INT result)
--* Run an external program with FDs connected to channels.
-- Like [@ref proc.run], but the child process's standard input, output and
-- error file descriptors are connected to the given occam channels.
--
-- For example:
--
-- [@code
-- PROC lsdemo (CHAN BYTE kyb?, scr!, err!)
--   VAL [][]BYTE command IS ["/bin/ls", ['-', 'a', 'l', 0, 0, 0, 0]]:
--   INT result:
--   SEQ
--     proc.wrapper ([], command, kyb, scr, err, result)
-- :]
--
-- @param env Additional environment variables
-- @param command The process name and arguments
-- @param kyb.in Channel to connect to the child process's standard input
-- @param scr.out Channel to connect to the child process's standard output
-- @param err.out Channel to connect to the child process's standard error
-- @param result [@code -1] if an error occurred, otherwise the exit status of
--   the process
PROC proc.wrapper (VAL []ENVIRONMENT env, VAL [][]BYTE command, CHAN OF BYTE kyb.in, scr.out, err.out, INT result)

  PROC occam.to.fd.copy (CHAN OF BYTE in, VAL INT fd, CHAN OF BOOL term)
    INITIAL BOOL running IS TRUE:
    [1]BYTE buffer:
    INT result:
    WHILE running
      PRI ALT
        in ? buffer[0]
          file.blocking.write (fd, buffer, result)
        term ? running
          SKIP
  :

  PROC fd.to.occam.copy (VAL INT fd, CHAN OF BYTE out)
    [128]BYTE buffer:
    INT result:
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        file.blocking.read (fd, buffer, result)
        IF
          result < 1
            running := FALSE
          TRUE
            SEQ i = 0 FOR result
              out ! buffer[i]
  :

  INT kyb.rd, kyb.wr:
  INT scr.rd, scr.wr:
  INT err.rd, err.wr:
  INT res:
  SEQ
    file.pipe (kyb.rd, kyb.wr, res)
    result := 0
    IF
      res < 0
        result := (-1)
      TRUE
        SEQ
          file.pipe (scr.rd, scr.wr, res)
          IF
            res < 0
              INT r:
              SEQ
                file.close (kyb.rd, r)
                file.close (kyb.wr, r)
                result := (-1)
            TRUE
              SEQ
                file.pipe (err.rd, err.wr, res)
                IF
                  res < 0
                    INT r:
                    SEQ
                      file.close (kyb.rd, r)
                      file.close (kyb.wr, r)
                      file.close (scr.rd, r)
                      file.close (scr.wr, r)
                      result := (-1)
                  TRUE
                    SKIP
    IF
      result = 0
        INT r:
        SEQ
          #PRAGMA DEFINED scr.rd, err.rd, scr.wr, err.wr
          CHAN OF BOOL kbd.term:
          PAR
            occam.to.fd.copy (kyb.in, kyb.wr, kbd.term)
            fd.to.occam.copy (scr.rd, scr.out)
            fd.to.occam.copy (err.rd, err.out)
            SEQ
              proc.run (env, command, kyb.rd, scr.wr, err.wr, result)
              file.close (kyb.rd, r)
              file.close (scr.wr, r)
              file.close (err.wr, r)
              kbd.term ! FALSE
          file.close (kyb.wr, r)
          file.close (scr.rd, r)
          file.close (err.rd, r)
      TRUE
        SKIP
:
--}}}  

#IF DEFINED (OCCBUILD.KROC)
--{{{  PROC proc.altable.wrapper (CHAN OF BOOL kill, CHAN OF INT response, VAL []ENVIRONMENT env, VAL [][]BYTE command, CHAN OF BYTE kyb.in, scr.out, err.out, INT result)
--* [@code ALT]able version of [@ref proc.wrapper].
PROC proc.altable.wrapper (CHAN OF BOOL kill, CHAN OF INT response, VAL []ENVIRONMENT env, VAL [][]BYTE command, CHAN OF BYTE kyb.in, scr.out, err.out, INT result)

  PROC occam.to.fd.copy (CHAN OF BYTE in, VAL INT fd, CHAN OF BOOL term)
    INITIAL BOOL running IS TRUE:
    [1]BYTE buffer:
    INT result:
    WHILE running
      PRI ALT
        in ? buffer[0]
          file.blocking.write (fd, buffer, result)
        term ? running
          SKIP
  :

  PROC fd.to.occam.copy (VAL INT fd, CHAN OF BYTE out)
    [128]BYTE buffer:
    INT result:
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        file.blocking.read (fd, buffer, result)
        IF
          result < 1
            running := FALSE
          TRUE
            SEQ i = 0 FOR result
              out ! buffer[i]
  :

  INT kyb.rd, kyb.wr:
  INT scr.rd, scr.wr:
  INT err.rd, err.wr:
  INT res:
  [3]INT rd.fds, wr.fds:
  SEQ
    file.npipes (rd.fds, wr.fds, res)
    kyb.rd, scr.rd, err.rd := rd.fds[0], rd.fds[1], rd.fds[2]
    kyb.wr, scr.wr, err.wr := wr.fds[0], wr.fds[1], wr.fds[2]
    result := 0
    IF
      res < 0
        result := (-1)
      TRUE
        SKIP
    IF
      result = 0
        INT r:
        SEQ
          CHAN OF BOOL kbd.term:
          PAR
            occam.to.fd.copy (kyb.in, kyb.wr, kbd.term)
            fd.to.occam.copy (scr.rd, scr.out)
            fd.to.occam.copy (err.rd, err.out)
            SEQ
              proc.altable.run (kill, response, env, command, kyb.rd, scr.wr, err.wr, result)
              file.close (kyb.rd, r)
              file.close (scr.wr, r)
              file.close (err.wr, r)
              kbd.term ! FALSE
          file.close (kyb.wr, r)
          file.close (scr.rd, r)
          file.close (err.rd, r)
      TRUE
        SKIP
:
--}}}  
#ENDIF

--{{{  PROC proc.setenv (ENVIRONMENT env, VAL []BYTE ident, value)
--* Fill in an [@ref ENVIRONMENT] record.
-- This copies the given values into the record, and also sets the size fields
-- correctly.
--
-- @param env The [@ref ENVIRONMENT] to set
-- @param ident The variable name
-- @param value The variable value
PROC proc.setenv (ENVIRONMENT env, VAL []BYTE ident, value)
  SEQ
    [env[ident] FOR (SIZE ident)] := ident
    [env[value] FOR (SIZE value)] := value
    env[ident.size] := (SIZE ident)
    env[value.size] := (SIZE value)
:
--}}}  

--{{{  PROC proc.os.getenv (ENVIRONMENT env, VAL []BYTE ident)
--* Get an environment variable.
-- This reads the environment of the occam program.
--
-- @param env Environment variable returned
-- @param ident The variable name to look up
PROC proc.os.getenv (ENVIRONMENT env, VAL []BYTE ident)
  SEQ
    C.pl.os.getenv (env, ident)
:
--}}}  

--{{{  PROC proc.os.setenv (VAL ENVIRONMENT env)
--* Set an environment variable.
-- This sets a variable in the environment of the occam program (and thus in
-- the environment of any programs it runs in the future).
--
-- @param env Environment variable to set
PROC proc.os.setenv (VAL ENVIRONMENT env)
  SEQ
    C.pl.os.setenv ([env])
:
--}}}  

--{{{  PROC proc.os.setenv.array (VAL []ENVIRONMENT env)
--* Set several environment variables.
-- This is semantically equivalent to calling [@ref proc.os.setenv] several
-- times, but may be more efficient if you're setting several variables.
--
-- @param env Environment variables to set
PROC proc.os.setenv.array (VAL []ENVIRONMENT env)
  SEQ
    C.pl.os.setenv (env)
:
--}}}  

