--
--	filelib.occ - occam file 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.
--

--** File library.
--
-- This library provides a subset of the standard POSIX file-handling
-- functions, with some additions. Many of the provided processes
-- ([@ref file.open], [@ref file.close], ...) are implemented as direct C calls
-- rather than as blocking calls, as they are unlikely to block in the OS
-- kernel.
--
-- This may be used as a replacement for the hostio/hostsp libraries when
-- writing applications for POSIX platforms.
--
-- To use this library:
--
-- [@code
-- #INCLUDE "file.module"]
--
-- @module file

#INCLUDE "filelib.inc"
#IF DEFINED (OCCBUILD.TVM)
#INCLUDE "tvmspecials.inc"
#ENDIF

--{{{  externals
#PRAGMA EXTERNAL "PROC C.fl.check.access (VAL []BYTE filename, VAL INT what, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.size (VAL []BYTE filename, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.open (VAL []BYTE filename, VAL INT mode, RESULT INT fd) = 0"
#PRAGMA EXTERNAL "PROC C.fl.open3 (VAL []BYTE filename, VAL INT mode, VAL INT perm, RESULT INT fd) = 0"
#PRAGMA EXTERNAL "PROC C.fl.pipe (RESULT INT fd.0, fd.1, result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.dup2 (VAL INT old.fd, VAL INT new.fd, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.read (VAL INT fd, []BYTE buffer, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC B.fl.read (VAL INT fd, []BYTE buffer, RESULT INT result) = 3"
#PRAGMA EXTERNAL "PROC C.fl.write (VAL INT fd, VAL []BYTE buffer, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC B.fl.write (VAL INT fd, VAL []BYTE buffer, RESULT INT result) = 3"
#PRAGMA EXTERNAL "PROC C.fl.seek (VAL INT fd, VAL INT offset, VAL INT whence, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.close (VAL INT fd, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.mkdir (VAL []BYTE pathname, VAL INT perm, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.rmdir (VAL []BYTE pathname, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.unlink (VAL []BYTE filename, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.fd.fd.copy (VAL INT src.fd, dst.fd, count, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC B.fl.fd.fd.copy (VAL INT src.fd, dst.fd, count, RESULT INT result) = 3"
#PRAGMA EXTERNAL "PROC C.fl.sendfile (VAL INT src.fd, dst.fd, count, INT offset, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC B.fl.sendfile (VAL INT src.fd, dst.fd, count, INT offset, RESULT INT result) = 3"
#IF DEFINED (OCCBUILD.TVM)
#PRAGMA EXTERNAL "PROC C.fl.tvm.set.args (VAL INT argc, argv) = 0"
#ENDIF
#PRAGMA EXTERNAL "PROC C.fl.num.args (RESULT INT n) = 0"
#PRAGMA EXTERNAL "PROC C.fl.nth.arg (VAL INT n, RESULT []BYTE arg, RESULT INT len) = 0"

#IF DEFINED (OCCBUILD.KROC)
#PRAGMA EXTERNAL "PROC C.killcall (CHAN OF INT c, RESULT INT result) = 0"
#ENDIF

#PRAGMA EXTERNAL "PROC C.fl.stat (VAL []BYTE filename, RESULT STAT result, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.lstat (VAL []BYTE filename, RESULT STAT result, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.fstat (VAL INT fd, RESULT STAT result, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.opendir (VAL []BYTE filename, RESULT INT dd) = 0"
#PRAGMA EXTERNAL "PROC C.fl.readdir (VAL INT dd, DIRENT info, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.closedir (VAL INT dd, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.chmod (VAL []BYTE filename, INT mode, RESULT INT res) = 0"
#PRAGMA EXTERNAL "PROC C.fl.fsync (VAL INT fd, RESULT INT result) = 0"

#PRAGMA EXTERNAL "PROC C.fl.fcntl0 (VAL INT fd, cmd, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.fcntl1 (VAL INT fd, cmd, arg, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC C.fl.select ([]INT read.set, write.set, except.set, VAL INT high.fd, timeout, RESULT INT result) = 0"
#PRAGMA EXTERNAL "PROC B.fl.select ([]INT read.set, write.set, except.set, VAL INT high.fd, timeout, RESULT INT result) = 3"
--}}}  
--{{{  PROC file.check.access (VAL []BYTE filename, VAL INT what, BOOL result)
--* Check access to a filesystem object.
-- @param filename The filename to check
-- @param what Bitwise OR of permissions to test (see [@ref ACCESS])
-- @param result [@code TRUE] if the access check succeeded, else [@code FALSE]
PROC file.check.access (VAL []BYTE filename, VAL INT what, BOOL result)
  INT res:
  SEQ
    C.fl.check.access (filename, what, res)
    IF
      res < 0
        result := FALSE
      TRUE
        result := TRUE
:
--}}}  
--{{{  PROC file.size (VAL []BYTE filename, INT result)
--* Get the size of a filesystem object.
-- @param filename The filename to examine
-- @param result The size of the object in bytes, or [@code -1] if it does not
--   exist
PROC file.size (VAL []BYTE filename, INT result)
  SEQ
    C.fl.size (filename, result)
:
--}}}  
--{{{  PROC file.open (VAL []BYTE filename, VAL INT mode, INT fd)
--* Open a file descriptor.
-- This form should be used when [@ref O.CREAT] is not among the modes.
-- @param filename The filename to open
-- @param mode Bitwise OR of the file descriptor's modes (see [@ref OPEN.MODE])
-- @param fd The file descriptor, or [@code -1] if it cannot be opened
PROC file.open (VAL []BYTE filename, VAL INT mode, INT fd)
  SEQ
    C.fl.open (filename, mode, fd)
:
--}}}  
--{{{  PROC file.open3 (VAL []BYTE filename, VAL INT mode, VAL INT perm, INT fd)
--* Open a file descriptor.
-- This form should be used when [@ref O.CREAT] is among the modes.
-- @param filename The filename to open
-- @param mode Bitwise OR of the file descriptor's modes (see [@ref OPEN.MODE])
-- @param perm The permissions of [@code filename] after creation
-- @param fd The file descriptor, or [@code -1] if it cannot be opened
PROC file.open3 (VAL []BYTE filename, VAL INT mode, VAL INT perm, INT fd)
  SEQ
    C.fl.open3 (filename, mode, perm, fd)
:
--}}}  
--{{{  PROC file.pipe (INT fd.0, fd.1, result)
--* Create a pipe.
--
-- Example that copies a string by sending it through a pipe:
--
-- [@code
-- INT fd.read, fd.write:
-- INT result, result.rd, result.wr:
-- VAL []BYTE string IS "hello world!":
-- [(SIZE string)]BYTE buffer:
-- SEQ
--   file.pipe (fd.read, fd.write, result)
--   ASSERT (result = 0)
--   PAR
--     file.blocking.read (fd.read, buffer, (SIZE string), result.rd)
--     file.write (fd.write, string, (SIZE string), result.wr)
--   file.close (fd.read, result)
--   ASSERT (result = 0)
--   file.close (fd.write, result)
--   ASSERT (result = 0)]
--
-- See [@ref file.npipes] if you're creating several pipes.
--
-- @param fd.0 File descriptor for the read end of the pipe
-- @param fd.1 File descriptor for the write end of the pipe
-- @param result [@code 0] if the pipe was created, or [@code -1] if an error
--   occurred
PROC file.pipe (INT fd.0, fd.1, result)
  SEQ
    C.fl.pipe (fd.0, fd.1, result)
:
--}}}  
--{{{  PROC file.dup2 (VAL INT old.fd, VAL INT new.fd, INT result)
--* Make a copy of a file descriptor.
-- [@code new.fd] will be closed first if it is already open.
-- @param old.fd The source descriptor
-- @param new.fd The destination descriptor
-- @param result [@code 0] if successful, [@code -1] if an error occurred
PROC file.dup2 (VAL INT old.fd, VAL INT new.fd, INT result)
  SEQ
    C.fl.dup2 (old.fd, new.fd, result)
:
--}}}  
--{{{  PROC file.read (VAL INT fd, []BYTE buffer, INT result)
--* Read from a file descriptor into a buffer.
-- No more than [@code SIZE buffer] bytes will be read from the descriptor.
--
-- [@em Note:] [@code file.read] assumes that data is available (e.g. reading
-- from a file), and is not a blocking call; if the read could block, use
-- [@ref file.blocking.read] instead.
--
-- @param fd The file descriptor
-- @param buffer The buffer
-- @param result The number of bytes read; or [@code 0] if at end of file; or
--   [@code -1] if an error occurred
PROC file.read (VAL INT fd, []BYTE buffer, INT result)
  SEQ
    C.fl.read (fd, buffer, result)
:
--}}}  
--{{{  PROC file.blocking.read (VAL INT fd, []BYTE buffer, INT result)
--* Read from a file descriptor into a buffer without blocking the occam system.
-- No more than [@code SIZE buffer] bytes will be read from the descriptor.
--
-- @param fd The file descriptor
-- @param buffer The buffer
-- @param result The number of bytes read; or [@code 0] if at end of file; or
--   [@code -1] if an error occurred
PROC file.blocking.read (VAL INT fd, []BYTE buffer, INT result)
  SEQ
    B.fl.read (fd, buffer, result)
:
--}}}  
--{{{  PROC file.write (VAL INT fd, VAL []BYTE buffer, INT result)
--* Write from a buffer to a file descriptor.
-- No more than [@code SIZE buffer] bytes will be written.
--
-- [@em Note:] [@code file.write] assumes that data can always be written (e.g.
-- writing to a file), and is not a blocking call; if the write could block,
-- use [@ref file.blocking.write] instead.
--
-- @param fd The file descriptor
-- @param buffer The buffer
-- @param result The number of bytes written, or [@code -1] if an error
--   occurred
PROC file.write (VAL INT fd, VAL []BYTE buffer, INT result)
  SEQ
    C.fl.write (fd, buffer, result)
:
--}}}  
--{{{  PROC file.blocking.write (VAL INT fd, VAL []BYTE buffer, INT result)
--* Write from a buffer to a file descriptor without blocking the occam system.
-- No more than [@code SIZE buffer] bytes will be written.
-- @param fd The file descriptor
-- @param buffer The buffer
-- @param result The number of bytes written, or [@code -1] if an error
--   occurred
PROC file.blocking.write (VAL INT fd, VAL []BYTE buffer, INT result)
  SEQ
    B.fl.write (fd, buffer, result)
:
--}}}  
--{{{  PROC file.seek (VAL INT fd, VAL INT offset, VAL INT whence, INT result)
--* Move the read/write offset of a file descriptor.
-- @param fd The file descriptor
-- @param offset The offset from the location in [@code whence]
-- @param whence The base location; one of the [@ref SEEK] constants
-- @param result The new absolute file offset, or [@code -1] if an error
--   occurred
PROC file.seek (VAL INT fd, VAL INT offset, VAL INT whence, INT result)
  SEQ
    C.fl.seek (fd, offset, whence, result)
:
--}}}  
--{{{  PROC file.close (VAL INT fd, RESULT INT result)
--* Close a file descriptor.
-- @param fd The file descriptor
-- @param result [@code 0] upon success; [@code -1] if an error occurred
PROC file.close (VAL INT fd, RESULT INT result)
  SEQ
    C.fl.close (fd, result)
:
--}}}  
--{{{  PROC file.mkdir (VAL []BYTE pathname, VAL INT perm, INT result)
--* Create a directory.
-- @param pathname Name of the directory
-- @param perm Permissions (see [@ref FILE.MODE])
-- @param result [@code 0] upon success; [@code -1] if an error occurred
PROC file.mkdir (VAL []BYTE pathname, VAL INT perm, INT result)
  SEQ
    C.fl.mkdir (pathname, perm, result)
:
--}}}  
--{{{  PROC file.rmdir (VAL []BYTE pathname, INT result)
--* Remove a directory.
-- The directory must be empty before it can be removed.
-- @param pathname Name of the directory
-- @param result [@code 0] upon success; [@code -1] if an error occurred
PROC file.rmdir (VAL []BYTE pathname, INT result)
  SEQ
    C.fl.rmdir (pathname, result)
:
--}}}  
--{{{  PROC file.unlink (VAL []BYTE filename, INT result)
--* Unlink a filesystem object.
-- This can be used to remove anything that isn't a directory.
-- @param filename Name of the object
-- @param result [@code 0] upon success; [@code -1] if an error occurred
PROC file.unlink (VAL []BYTE filename, INT result)
  SEQ
    C.fl.unlink (filename, result)
:
--}}}  
--{{{  PROC file.fd.fd.copy (VAL INT src.fd, dst.fd, INT result)
--* Copy bytes between file descriptors.
-- The copy is performed in the background to reduce overhead.
-- @param src.fd The source file descriptor
-- @param dst.fd The destination file descriptor
-- @param count The number of bytes to copy, or [@code 0] to copy until end of
--   file on [@code src.fd]
-- @param result [@code 0] if the copy completed successfully, or [@code count]
--   was [@code 0] and [@code src.fd] was at end of file; [@code -1] if an
--   error occurred; [@code -3] if [@code src.fd] reached end of file before
--   [@code count] bytes had been copied
PROC file.fd.fd.copy (VAL INT src.fd, dst.fd, count, INT result)
  SEQ
    B.fl.fd.fd.copy (src.fd, dst.fd, count, result)
:
--}}}  
--{{{  PROC file.sendfile (VAL INT src.fd, dst.fd, count, INT offset, result)
--* Copy bytes between file descriptors efficiently.
-- This uses the [@text sendfile(2)] system call to avoid copying data between
-- userspace and kernelspace. Either or both descriptors may refer to a socket.
-- @param src.fd The source file descriptor
-- @param dst.fd The destination file descriptor
-- @param count The number of bytes to copy
-- @param offset The offset (in [@code src.fd]) to start copying from. After a
--   successful copy, this is updated to point at the byte following the last
--   one read.
-- @param result The number of bytes written, or [@code -1] if an error
--   occurred.
PROC file.sendfile (VAL INT src.fd, dst.fd, count, INT offset, result)
  SEQ
    B.fl.sendfile (src.fd, dst.fd, count, offset, result)
:
--}}}  
--{{{  PROC file.npipes ([]INT readers, writers, INT result)
--* Create several pipes using [@ref file.pipe].
-- @param readers The read ends of the pipes
-- @param writers The write ends of the pipes
-- @param result [@code 0] if the pipes were created, or [@code -1] if an error
--   occurred
PROC file.npipes ([]INT readers, writers, INT result)
  INT i:
  BOOL failed:
  SEQ
    ASSERT ((SIZE readers) = (SIZE writers))
    i := 0
    failed := FALSE
    WHILE (i < (SIZE readers)) AND (NOT failed)
      SEQ
        file.pipe (readers[i], writers[i], result)
        IF
          result < 0
            failed := TRUE
          TRUE
            i := (i + 1)
    IF
      failed
        -- close pipes that were created
        SEQ j = 0 FOR i
          INT r:
          SEQ
            file.close (readers[i], r)
            file.close (writers[i], r)
      TRUE
        SKIP
:
--}}}  
#IF DEFINED (OCCBUILD.KROC)
--{{{  PROC file.altable.read (CHAN OF BOOL kill, CHAN OF INT response, VAL INT fd, []BYTE buffer, INT result)
--* [@code ALT]able equivalent of [@ref file.blocking.read].
PROC file.altable.read (CHAN OF BOOL kill, CHAN OF INT response, VAL INT fd, []BYTE buffer, INT result)
  CHAN OF INT c, signal:
  PAR
    --{{{  make blocking call
    INT tres:
    SEQ
      BX.fl.read (c, fd, buffer, tres)
      signal ! tres
    --}}}  
    --{{{  complete or terminate
    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
      --}}}  
    --}}}  
:
--}}}  
--{{{  PROC file.altable.write (CHAN OF BOOL kill, CHAN OF INT response, VAL INT fd, VAL []BYTE buffer, INT result)
--* [@code ALT]able equivalent of [@ref file.blocking.write].
PROC file.altable.write (CHAN OF BOOL kill, CHAN OF INT response, VAL INT fd, VAL []BYTE buffer, INT result)
  CHAN OF INT c, signal:
  PAR
    --{{{  make blocking call
    INT tres:
    SEQ
      BX.fl.write (c, fd, buffer, tres)
      signal ! tres
    --}}}  
    --{{{  complete or terminate
    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
      --}}}  
    --}}}
:
--}}}  
--{{{  PROC file.altable.fd.fd.copy (CHAN OF BOOL kill, CHAN OF INT response, VAL INT src.fd, dst.fd, count, INT result)
--* [@code ALT]able equivalent of [@ref file.fd.fd.copy].
PROC file.altable.fd.fd.copy (CHAN OF BOOL kill, CHAN OF INT response, VAL INT src.fd, dst.fd, count, INT result)
  CHAN OF INT c, signal:
  PAR
    --{{{  make blocking call
    INT tres:
    SEQ
      BX.fl.fd.fd.copy (c, src.fd, dst.fd, count, tres)
      signal ! tres
    --}}}  
    --{{{  completion or termination
    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 file.array.select ([]INT read.set, write.set, except.set, VAL INT high.fd, timeout, RESULT INT result)
--* Wait for one of a set of file descriptors to become ready.
-- This performs the [@code select(2)] system call.
--
-- After [@code file.array.select] returns, the file descriptors in the sets
-- that are [@em not] ready are changed to [@code -1]. Any file descriptors
-- passed that are already [@code -1] are ignored.
--
-- @param read.set The set of reading file descriptors
-- @param write.set The set of writing file descriptors
-- @param except.set The set of exception-watching file descriptors
-- @param high.fd The highest file descriptor in all three sets, plus one;
--   you can use [@ref file.high.fd] to calculate this
-- @param timeout The time to wait for a file descriptor to become ready; or
--   [@code 0] to return immediately if none are ready; or [@code -1] to wait
--   indefinitely
-- @param result The number of ready file descriptors in the three sets (which
--   may be [@code 0] if the timeout passed before any were ready), or
--   [@code -1] if an error occurred
PROC file.array.select ([]INT read.set, write.set, except.set, VAL INT high.fd, timeout, RESULT INT result)
  SEQ
    B.fl.select (read.set, write.set, except.set, high.fd, timeout, result)
:
--}}}
--{{{  INT FUNCTION file.high.fd (VAL []INT read.set, write.set, except.set)
--* Find the highest file descriptor in three sets.
-- This is for use with [@ref file.array.select].
-- @param read.set The set of reading file descriptors
-- @param write.set The set of writing file descriptors
-- @param except.set The set of exception-watching file descriptors
-- @return The highest file descriptor in the sets; add one to
--   this to get the value to pass to [@ref file.array.select]
INT FUNCTION file.high.fd (VAL []INT read.set, write.set, except.set)
  INLINE PROC do.set (VAL []INT fds, INT h)
    SEQ i = 0 FOR SIZE fds
      IF
        fds[i] > h
          h := fds[i]
        TRUE
          SKIP
  :
  INT h:
  VALOF
    SEQ
      h := -1
      do.set (read.set, h)
      do.set (write.set, h)
      do.set (except.set, h)
    RESULT h
:
--}}}
--{{{  PROC file.fcntl0 (VAL INT fd, cmd, RESULT INT result)
--* Perform various operations on file descriptors.
-- This allows occam-pi programs to make a [@text fcntl(2)] system call with no
-- optional parameter. See the [@text fcntl(2)] manual page for more
-- information.
-- @param fd The (source) file descriptor
-- @param cmd The command (one of [@ref FCNTL], such as [@ref F.GETFD] or
--   [@ref F.GETFL])
-- @param result The result of the operation
PROC file.fcntl0 (VAL INT fd, cmd, RESULT INT result)
  SEQ
    C.fl.fcntl0 (fd, cmd, result)
:
--}}}
--{{{  PROC file.fcntl1 (VAL INT fd, cmd, arg, RESULT INT result)
--* Perform various operations on file descriptors.
-- This allows occam-pi programs to make a [@text fcntl(2)] system call with
-- one optional parameter. See the [@text fcntl(2)] manual page for more
-- information.
-- @param fd The (source) file descriptor
-- @param cmd The command (one of [@ref FCNTL], such as [@ref F.DUPFD],
--   [@ref F.SETFD] or [@ref F.SETFL])
-- @param arg A parameter for the operation
-- @param result The result of the operation
PROC file.fcntl1 (VAL INT fd, cmd, arg, RESULT INT result)
  SEQ
    C.fl.fcntl1 (fd, cmd, arg, result)
:
--}}}
--{{{  PROC file.num.args (RESULT INT n)
--* Get number of program arguments.
-- This is the equivalent of [@text argc] in C.
-- Programs always have at least one argument: their own name.
-- @param n The number of arguments the program was given
PROC file.num.args (RESULT INT n)
  SEQ
#IF DEFINED (OCCBUILD.TVM)
    INT argc, argv:
    SEQ
      C.tvmspecial.5.get.cmdline.ptr (argc, argv)
      C.fl.tvm.set.args (argc, argv)
#ENDIF
    C.fl.num.args (n)
:
--}}}
--{{{  PROC file.nth.arg (VAL INT n, RESULT []BYTE arg, RESULT INT len)
--* Get a program argument.
-- This is the equivalent of [@text argv[n]] in C.
--
-- For example, if the occam-pi program was invoked as
-- [@text ./argtest foo "bar foo"], then argument 1 will be
-- [@text foo], argument 2 will be [@text bar foo],
-- and [@ref file.num.args] will return 3.
--
-- If you want to parse Unix-style command-line options, use
-- [@ref file.get.options] instead.
--
-- @param n The number of the argument to fetch
-- @param arg The buffer to copy the argument into
-- @param len The length of the argument in bytes, or [@code SIZE arg] if the
--   argument was too long to fit in the buffer
PROC file.nth.arg (VAL INT n, RESULT []BYTE arg, RESULT INT len)
  SEQ
#IF DEFINED (OCCBUILD.TVM)
    INT argc, argv:
    SEQ
      C.tvmspecial.5.get.cmdline.ptr (argc, argv)
      C.fl.tvm.set.args (argc, argv)
#ENDIF
    C.fl.nth.arg (n, arg, len)
:
--}}}
--{{{  PROC file.get.options (VAL []BYTE options, CHAN GETOPT out!)
--* Parse command-line options.
-- This is intended to behave like POSIX (not GNU) [@text getopt(3)].
--
-- The [@code options] argument is a string listing the possible
-- (single-character) options for the program. Options that can take an
-- argument should be followed by a colon.
--
-- Example, for a program which can take [@text -v] and [@text -h HOST]
-- arguments:
--
-- [@code
-- CHAN GETOPT opts:
-- PAR
--   file.get.options ("vh:", opts!)
--   INITIAL BOOL running IS TRUE:
--   WHILE running
--     BYTE o:
--     MOBILE []BYTE a:
--     opts ? CASE
--       opt; o
--         CASE o
--           'v'
--             ...  handle -v option
--       opt.arg; o; a
--         CASE o
--           'h'
--             ...  handle -h option; argument is in a
--       arg; a
--         ...  handle non-option argument a
--       bad; a
--         ...  print usage message and exit
--       done
--         running := FALSE
-- ]
--
-- @param options List of options that this program can take
-- @param out Channel down which options are reported (using [@ref GETOPT])
PROC file.get.options (VAL []BYTE options, CHAN GETOPT out!)
  --{{{  constants
  VAL INT MAX.ARG.LENGTH IS 4096:
  VAL INT TYPE.NONE IS 0:
  VAL INT TYPE.OPT IS 1:
  VAL INT TYPE.ARG IS 2:
  --}}}
  --{{{  INT FUNCTION option.type (VAL BYTE o)
  INT FUNCTION option.type (VAL BYTE o)
    INT type:
    VALOF
      IF
        IF i = 0 FOR SIZE options
          options[i] = o
            IF
              i = ((SIZE options) - 1)
                type := TYPE.OPT
              options[i + 1] = ':'
                type := TYPE.ARG
              TRUE
                type := TYPE.OPT
        TRUE
          type := TYPE.NONE
      RESULT type
  :
  --}}}
  --{{{  BOOL FUNCTION starts.with.string (VAL []BYTE needle, haystack)
  BOOL FUNCTION starts.with.string (VAL []BYTE needle, haystack)
    BOOL result:
    VALOF
      VAL INT nsize IS SIZE needle:
      IF
        nsize > (SIZE haystack)
          result := FALSE
        IF i = 0 FOR nsize
          needle[i] <> haystack[i]
            result := FALSE
        TRUE
          result := TRUE
      RESULT result
  :
  --}}}
  --{{{  BOOL FUNCTION same.string (VAL []BYTE a, b)
  BOOL FUNCTION same.string (VAL []BYTE a, b)
    BOOL result:
    VALOF
      IF
        (SIZE a) <> (SIZE b)
          result := FALSE
        IF i = 0 FOR (SIZE a)
          a[i] <> b[i]
            result := FALSE
        TRUE
          result := TRUE
      RESULT result
  :
  --}}}
  CHAN GETOPT inter:
  PAR
    --{{{  get command-line arguments
    INT num.args:
    SEQ
      file.num.args (num.args)
      SEQ i = 1 FOR num.args - 1
        [MAX.ARG.LENGTH]BYTE arg:
        INT len:
        SEQ
          file.nth.arg (i, arg, len)
          inter ! arg ; [arg FOR len]
      inter ! done
    --}}}
    --{{{  parse options
    SEQ
      INITIAL BOOL running IS TRUE:
      WHILE running
        inter ? CASE
          MOBILE []BYTE arg:
          --{{{  PROC bad (VAL BYTE o)
          PROC bad (VAL BYTE o)
            SEQ
              WHILE running
                inter ? CASE
                  arg ; arg
                    SKIP
                  done
                    running := FALSE
              out ! bad ; o
          :
          --}}}
          arg ; arg
            IF
              starts.with.string ("-", arg) AND (NOT same.string (arg, "--"))
                --{{{  option argument
                INITIAL BOOL scanning IS TRUE:
                INITIAL INT pos IS 1:
                WHILE scanning AND (pos < (SIZE arg))
                  INITIAL BYTE o IS arg[pos]:
                  CASE option.type (o)
                    TYPE.NONE
                      SEQ
                        --{{{  unknown option
                        bad (o)
                        scanning := FALSE
                        --}}}
                    TYPE.OPT
                      SEQ
                        --{{{  plain option
                        out ! opt ; o
                        pos := pos + 1
                        --}}}
                    TYPE.ARG
                      SEQ
                        --{{{  option with argument
                        pos := pos + 1
                        IF
                          pos < (SIZE arg)
                            --{{{  arg is the rest of this argument
                            VAL INT len IS (SIZE arg) - pos:
                            out ! opt.arg ; o ; [arg FROM pos FOR len]
                            --}}}
                          TRUE
                            --{{{  arg is next argument
                            inter ? CASE
                              arg ; arg
                                out ! opt.arg ; o ; arg
                              done
                                SEQ
                                  --{{{  ... but there isn't one
                                  running := FALSE
                                  bad (o)
                                  --}}}
                            --}}}
                        scanning := FALSE
                        --}}}
                --}}}
              TRUE
                --{{{  first non-option argument; pass the rest through
                WHILE running
                  SEQ
                    out ! arg ; arg
                    inter ? CASE
                      arg ; arg
                        SKIP
                      done
                        running := FALSE
                --}}}
          done
            running := FALSE
      out ! done
    --}}}
:
--}}}
--{{{  PROC file.stat (VAL []BYTE name, RESULT STAT info, RESULT INT res)
--* Get information about a filesystem object.
-- Symbolic links will be followed.
-- @param name The filename to examine
-- @param info The returned information (see [@ref STAT])
-- @param res [@code 0] on success, [@code -1] on error
PROC file.stat (VAL []BYTE name, RESULT STAT info, RESULT INT res)
  C.fl.stat (name, info, res)
:
--}}}
--{{{  PROC file.lstat (VAL []BYTE name, RESULT STAT info, RESULT INT res)
--* Get information about a filesystem object or link.
-- Symbolic links will not be followed.
-- @param name The filename to examine
-- @param info The returned information (see [@ref STAT])
-- @param res [@code 0] on success, [@code -1] on error
PROC file.lstat (VAL []BYTE name, RESULT STAT info, RESULT INT res)
  C.fl.lstat (name, info, res)
:
--}}}
--{{{  PROC file.fstat (VAL INT fd, RESULT STAT info, RESULT INT res)
--* Get information about an open filesystem object.
-- @param fd The file descriptor to examine
-- @param info The returned information (see [@ref STAT])
-- @param res [@code 0] on success, [@code -1] on error
PROC file.fstat (VAL INT fd, RESULT STAT info, RESULT INT res)
  C.fl.fstat (fd, info, res)
:
--}}}
--{{{  PROC file.opendir (VAL []BYTE filename, RESULT INT dd)
--* Open a directory.
-- @param filename The directory path
-- @param dd The returned directory descriptor, or [@code -1] on error
PROC file.opendir (VAL []BYTE filename, RESULT INT dd)
  SEQ
    C.fl.opendir (filename, dd)
:
--}}}
--{{{  PROC file.readdir (VAL INT dd, RESULT DIRENT info, RESULT INT res)
--* Read the next directory entry from a directory.
-- @param dd The directory descriptor
-- @param info The returned directory entry (see [@ref DIRENT])
-- @param res [@code 0] on success, [@code -1] on error
PROC file.readdir (VAL INT dd, RESULT DIRENT info, RESULT INT res)
  C.fl.readdir (dd, info, res)
:
--}}}
--{{{  PROC file.closedir (VAL INT dd, RESULT INT res)
--* Close a directory descriptor.
-- @param dd The directory descriptor
-- @param res [@code 0] on success, [@code -1] on error
PROC file.closedir (VAL INT dd, RESULT INT res)
  SEQ
    C.fl.closedir (dd, res)
:
--}}}
--{{{  PROC file.chmod (VAL []BYTE filename, INT mode, RESULT INT res)
--* Change the permissions of a filesystem object.
-- @param filename The filename to change
-- @param mode The new permissions of the object (see [@ref FILE.MODE])
-- @param res [@code 0] on success, [@code -1] on error
PROC file.chmod (VAL []BYTE filename, INT mode, RESULT INT res)
  SEQ
    C.fl.chmod (filename, mode, res)
:
--}}}
--{{{  PROC file.fsync (VAL INT fd, RESULT INT result)
--* Force changes to an open file to be written to disk.
-- @param fd The file descriptor
-- @param result [@code 0] on success, [@code -1] on error
PROC file.fsync (VAL INT fd, RESULT INT result)
  SEQ
    C.fl.fsync (fd, result)
:
--}}}

