/****************************************************************************
**
**  This file is part of GAP, a system for computational discrete algebra.
**
**  Copyright of GAP belongs to its developers, whose names are too numerous
**  to list here. Please refer to the COPYRIGHT file for details.
**
**  SPDX-License-Identifier: GPL-2.0-or-later
**
**  This file contains the  various read-eval-print loops and streams related
**  stuff.  The system depend part is in "sysfiles.c".
*/

#include "streams.h"

#include "bool.h"
#include "calls.h"
#include "error.h"
#include "funcs.h"
#include "gap.h"
#include "gapstate.h"
#include "gaptime.h"
#include "gvars.h"
#include "integer.h"
#include "io.h"
#include "lists.h"
#include "modules.h"
#include "opers.h"
#include "plist.h"
#include "precord.h"
#include "read.h"
#include "records.h"
#include "stringobj.h"
#include "sysfiles.h"
#include "sysopt.h"
#include "sysroots.h"
#include "sysstr.h"
#include "trycatch.h"
#include "vars.h"

#include "config.h"

#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#ifdef HAVE_SELECT
// For FuncUNIXSelect
#include <sys/time.h>
#endif


static Obj IsInputStream;
static Obj IsOutputStream;

#define RequireInputStream(funcname, op)                                     \
    RequireArgumentCondition(funcname, op,                                   \
                             CALL_1ARGS(IsInputStream, op) == True,          \
                             "must be an input stream")

#define RequireOutputStream(funcname, op)                                    \
    RequireArgumentCondition(funcname, op,                                   \
                             CALL_1ARGS(IsOutputStream, op) == True,         \
                             "must be an output stream")


/****************************************************************************
**
*F * * * * * * * * * streams and files related functions  * * * * * * * * * *
*/

static UInt OpenInputFileOrStream(const char *   funcname,
                                  TypInputFile * input,
                                  Obj            inputObj)
{
    if (IsStringConv(inputObj)) {
        return OpenInput(input, CONST_CSTR_STRING(inputObj));
    }
    else if (CALL_1ARGS(IsInputStream, inputObj) == True) {
        return OpenInputStream(input, inputObj, FALSE);
    }
    RequireArgumentEx(funcname, inputObj, "<input>",
                      "must be a string or an input stream");
}

/****************************************************************************
**
*F  FuncREAD_ALL_COMMANDS( <self>, <instream>, <echo>, <capture>,
**                                                         <resultCallback> )
**
**  FuncREAD_ALL_COMMANDS attempts to execute all statements read from the
**  stream <instream>. It returns 'fail' if the stream cannot be opened,
**  otherwise a list of lists, each entry of which reflects the result of the
**  execution of one statement.
**
**  If the parameter <echo> is 'true', then the statements are echoed to the
**  current output.
**
**  If the parameter <capture> is 'true', then any output occurring during
**  execution of a statement, including the output of <resultCallback>, is
**  captured into a string.
**
**  If <resultCallback> is a function, then this function is called on every
**  statement result, otherwise this parameter is ignored. Possible outputs of
**  this function are captured if <capture> is 'true'.
**
**  The results are returned as lists of length at most five, the structure of
**  which is explained below:
**
**  - The first entry is 'true' if the statement was executed successfully,
**    and 'false' otherwise.
**
**  - If the first entry is 'true', then the second entry is bound to the
**    result of the statement if there was one, and unbound otherwise.
**
**  - The third entry is 'true' if the statement ended in a dual semicolon,
**    and 'false' otherwise.
**
**  - The fourth entry contains the return value of <resultCallback> if
**    applicable.
**
**  - The fifth entry contains the captured output as a string, if <capture>
**    is 'true'.
**
**  This function is currently used in interactive tools such as the GAP
**  Jupyter kernel to execute cells and is likely to be replaced by a function
**  that can read a single command from a stream without losing the rest of
**  its content.
*/
Obj READ_ALL_COMMANDS(Obj instream, Obj echo, Obj capture, Obj resultCallback)
{
    volatile Obj outstream = 0;
    volatile Obj outstreamString = 0;

    RequireInputStream("READ_ALL_COMMANDS", instream);

    // try to open the streams
    TypInputFile input;
    if (!OpenInputStream(&input, instream, echo == True)) {
        return Fail;
    }

    if (capture == True) {
        outstreamString = NEW_STRING(0);
        outstream = DoOperation2Args(ValGVar(GVarName("OutputTextString")),
                                     outstreamString, True);
    }
    TypOutputFile output;
    if (outstream && !OpenOutputStream(&output, outstream)) {
        CloseInput(&input);
        return Fail;
    }

    volatile Obj resultList = NEW_PLIST(T_PLIST, 16);
    BOOL rethrow = FALSE;

    GAP_TRY
    {
        while (1) {
            if (outstream) {
                // Clean in case there has been any output
                SET_LEN_STRING(outstreamString, 0);
            }

            BOOL dualSemicolon;
            Obj  evalResult;

            ExecStatus status = ReadEvalCommand(0, &input, &evalResult, &dualSemicolon);
            if (status == STATUS_EOF || status == STATUS_QUIT ||
                status == STATUS_QQUIT)
                break;

            Obj result = NEW_PLIST(T_PLIST, 5);
            AssPlist(result, 1, False);
            PushPlist(resultList, result);

            if (status != STATUS_ERROR) {

                AssPlist(result, 1, True);
                AssPlist(result, 3, dualSemicolon ? True : False);

                if (evalResult) {
                    AssPlist(result, 2, evalResult);
                }

                if (evalResult && IS_FUNC(resultCallback) && !dualSemicolon) {
                    Obj tmp = CALL_1ARGS(resultCallback, evalResult);
                    AssPlist(result, 4, tmp);
                }
            }
            // Capture output
            if (capture == True) {
                // Flush output
                Pr("\03", 0, 0);
                Obj copy = CopyToStringRep(outstreamString);
                SET_LEN_STRING(outstreamString, 0);
                AssPlist(result, 5, copy);
            }
        }
    }
    GAP_CATCH
    {
        rethrow = TRUE;
    }

    if (outstream)
        CloseOutput(&output);
    CloseInput(&input);

    if (rethrow)
        GAP_THROW();

    return resultList;
}

static Obj FuncREAD_ALL_COMMANDS(
    Obj self, Obj instream, Obj echo, Obj capture, Obj resultCallback)
{
    return READ_ALL_COMMANDS(instream, echo, capture, resultCallback);
}


/*
 Returns a list with one or two entries. The first
 entry is set to "false" if there was any error
 executing the command, and "true" otherwise.
 The second entry, if present, is the return value of
 the command. If it not present, the command returned nothing.
*/
static Obj FuncREAD_COMMAND_REAL(Obj self, Obj stream, Obj echo)
{
    Obj result;
    Obj evalResult;

    RequireInputStream(SELF_NAME, stream);

    result = NEW_PLIST(T_PLIST, 2);
    AssPlist(result, 1, False);

    // open the stream, read a command, and close it again
    TypInputFile input;
    if (!OpenInputStream(&input, stream, echo == True)) {
        return result;
    }
    ExecStatus status;

    GAP_TRY
    {
        status = ReadEvalCommand(0, &input, &evalResult, 0);
    }
    GAP_CATCH
    {
        CloseInput(&input);
        GAP_THROW();
    }
    CloseInput(&input);

    if (status == STATUS_EOF || status == STATUS_QQUIT)
        return result;
    else if (STATE(UserHasQuit) || STATE(UserHasQUIT))
        return result;
    else if (status == STATUS_RETURN)
        Pr("'return' must not be used in file read-eval loop\n", 0, 0);

    AssPlist(result, 1, True);
    if (evalResult) {
        AssPlist(result, 2, evalResult);
    }
    return result;
}

/****************************************************************************
**
*F  READ()  . . . . . . . . . . . . . . . . . . . . . . .  read current input
**
**  Read the current input and close the input stream.
*/

static UInt LastReadValueGVar;

static void READ_INNER(TypInputFile * input)
{
    if (STATE(UserHasQuit))
      {
        Pr("Warning: Entering READ with UserHasQuit set, this should never happen, resetting",0,0);
        STATE(UserHasQuit) = FALSE;
      }
    if (STATE(UserHasQUIT))
      {
        Pr("Warning: Entering READ with UserHasQUIT set, this should never happen, resetting",0,0);
        STATE(UserHasQUIT) = FALSE;
      }
    AssGVarWithoutReadOnlyCheck( LastReadValueGVar, 0);

    // now do the reading
    while ( 1 ) {
        Obj evalResult;
        ExecStatus status = ReadEvalCommand(0, input, &evalResult, 0);
        if (STATE(UserHasQuit) || STATE(UserHasQUIT))
            break;

        // handle return-value or return-void command
        if (status == STATUS_RETURN) {
            Pr("'return' must not be used in file read-eval loop\n", 0, 0);
        }

        // handle quit command or <end-of-file>
        else if (status == STATUS_EOF || status == STATUS_ERROR)
          break;
        else if (status == STATUS_QUIT) {
          STATE(UserHasQuit) = TRUE;
          break;
        }
        else if (status == STATUS_QQUIT) {
          STATE(UserHasQUIT) = TRUE;
          break;
        }
        if (evalResult)
          {
            AssGVarWithoutReadOnlyCheck( LastReadValueGVar, evalResult);
          }

    }
}


/****************************************************************************
**
*F  READ_AS_FUNC()  . . . . . . . . . . . . .  read current input as function
**
**  Read the current input as function and close the input stream.
*/
Obj READ_AS_FUNC(TypInputFile * input)
{
    // now do the reading
    Obj evalResult;
    ExecStatus status = ReadEvalFile(input, &evalResult);

    // get the function
    Obj func = (status == STATUS_END) ? evalResult : Fail;

    // return the function
    return func;
}


/****************************************************************************
**
*F  READ_GAP_ROOT( <filename> ) . . .  read from gap root, dyn-load or static
**
**  'READ_GAP_ROOT' tries to find  a file under  the root directory,  it will
**  search all   directories given   in 'SyGapRootPaths',  check  dynamically
**  loadable modules and statically linked modules.
*/
Int READ_GAP_ROOT ( const Char * filename )
{
    char path[GAP_PATH_MAX];

    // try to find the GAP file
    SyFindGapRootFile(filename, path, sizeof(path));

    // try to find compiled version of the GAP file
    if (SyUseModule) {
        // This code section covers transparently loading GAC compiled
        // versions of GAP source files, by running code similar to that in
        // FuncLOAD_STAT. For example, lib/oper1.g is compiled into C code;
        // when reading lib/oper1.g, we instead load its compiled version.
        Char module[GAP_PATH_MAX];
        strxcpy(module, "GAPROOT/", sizeof(module));
        strxcat(module, filename, sizeof(module));

        // search for a statically linked module matching the given filename
        StructInitInfo * info = LookupStaticModule(module);
        if (info) {
            // found a matching statically linked module; if there is also
            // a GAP file, compare their CRC
            if (*path && info->crc != SyGAPCRC(path)) {
                Pr("#W Static module %s has CRC mismatch, ignoring\n",
                   (Int)filename, 0);
            }
            else {
                if (SyDebugLoading) {
                    Pr("#I  READ_GAP_ROOT: loading '%s' statically\n",
                       (Int)filename, 0);
                }
                ActivateModule(info);
                RecordLoadedModule(info, 1, filename);
                return 1;
            }
        }
    }

    // not found?
    if (*path == 0)
        return 0;

#ifdef GAP_ENABLE_SAVELOAD
    // special handling case if we are trying to load compiled modules needed
    // for a saved workspace
    if (SyRestoring) {
        // ErrorQuit is not available
        Pr("Can't find compiled module '%s' needed by saved workspace\n",
           (Int)filename, 0);
        return 0;
    }
#endif

    // ordinary gap file
    if (SyDebugLoading) {
        Pr("#I  READ_GAP_ROOT: loading '%s' as GAP file\n", (Int)filename, 0);
    }

    TypInputFile input;
    if (!OpenInput(&input, path))
        return 0;

    GAP_TRY
    {
        while (1) {
            ExecStatus status = ReadEvalCommand(0, &input, 0, 0);
            if (STATE(UserHasQuit) || STATE(UserHasQUIT))
                break;
            if (status == STATUS_RETURN) {
                Pr("'return' must not be used in file", 0, 0);
            }
            else if (status == STATUS_EOF || status == STATUS_QUIT) {
                break;
            }
        }
    }
    GAP_CATCH
    {
        CloseInput(&input);
        GAP_THROW();
    }
    CloseInput(&input);
    return 1;
}


/****************************************************************************
**
*F  FuncCALL_WITH_STREAM( <stream>, <func>, <args> )
**
**  Temporarily set the active output stream to <stream>, then call the
**  function <func> with the arguments in the list <args>. This can for
**  example be used to capture the output of a function into a string.
*/
static Obj FuncCALL_WITH_STREAM(Obj self, Obj stream, Obj func, Obj args)
{
    RequireOutputStream(SELF_NAME, stream);
    RequireSmallList(SELF_NAME, args);

    TypOutputFile output;
    if (!OpenOutputStream(&output, stream)) {
        ErrorQuit("CALL_WITH_STREAM: cannot open stream for output", 0, 0);
    }

    Obj result;
    GAP_TRY
    {
        result = CallFuncList(func, args);
    }
    GAP_CATCH
    {
        CloseOutput(&output);
        GAP_THROW();
    }

    if (!CloseOutput(&output)) {
        ErrorQuit("CALL_WITH_STREAM: cannot close output", 0, 0);
    }

    return result;
}


/****************************************************************************
**
*F  FuncCLOSE_LOG_TO()  . . . . . . . . . . . . . . . . . . . .  stop logging
**
**  'FuncCLOSE_LOG_TO' implements a method for 'LogTo'.
**
**  'LogTo()'
**
**  'LogTo' called with no argument closes the current logfile again, so that
**  input   from  '*stdin*'  and  '*errin*'  and  output  to  '*stdout*'  and
**  '*errout*' will no longer be echoed to a file.
*/
static Obj FuncCLOSE_LOG_TO(Obj self)
{
    if ( ! CloseLog() ) {
        ErrorQuit("LogTo: cannot close the logfile", 0, 0);
    }
    return True;
}


/****************************************************************************
**
*F  FuncLOG_TO( <filename> ) . . . . . . . . . . . .  start logging to a file
**
**  'FuncLOG_TO' implements a method for 'LogTo'
**
**  'LogTo( <filename> )'
**
**  'LogTo' instructs GAP to echo all input from the  standard  input  files,
**  '*stdin*' and '*errin*' and all output  to  the  standard  output  files,
**  '*stdout*'  and  '*errout*',  to  the  file  with  the  name  <filename>.
**  The file is created if it does not  exist,  otherwise  it  is  truncated.
*/
static Obj FuncLOG_TO(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);
    if ( ! OpenLog( CONST_CSTR_STRING(filename) ) ) {
        ErrorReturnVoid("LogTo: cannot log to %g", (Int)filename, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncLOG_TO_STREAM( <stream> ) . . . . . . . . . start logging to a stream
*/
static Obj FuncLOG_TO_STREAM(Obj self, Obj stream)
{
    RequireOutputStream(SELF_NAME, stream);
    if ( ! OpenLogStream(stream) ) {
        ErrorReturnVoid("LogTo: cannot log to stream", 0, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncCLOSE_INPUT_LOG_TO()  . . . . . . . . . . . . . . . . .  stop logging
**
**  'FuncCLOSE_INPUT_LOG_TO' implements a method for 'InputLogTo'.
**
**  'InputLogTo()'
**
**  'InputLogTo' called with no argument closes the current logfile again, so
**  that input from  '*stdin*' and '*errin*' will   no longer be  echoed to a
**  file.
*/
static Obj FuncCLOSE_INPUT_LOG_TO(Obj self)
{
    if ( ! CloseInputLog() ) {
        ErrorQuit("InputLogTo: cannot close the logfile", 0, 0);
    }
    return True;
}


/****************************************************************************
**
*F  FuncINPUT_LOG_TO( <filename> )  . . . . . . . . . start logging to a file
**
**  'FuncINPUT_LOG_TO' implements a method for 'InputLogTo'
**
**  'InputLogTo( <filename> )'
**
**  'InputLogTo'  instructs  GAP to echo   all input from  the standard input
**  files, '*stdin*' and '*errin*' to the file with the name <filename>.  The
**  file is created if it does not exist, otherwise it is truncated.
*/
static Obj FuncINPUT_LOG_TO(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);
    if ( ! OpenInputLog( CONST_CSTR_STRING(filename) ) ) {
        ErrorReturnVoid("InputLogTo: cannot log to %g", (Int)filename, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncINPUT_LOG_TO_STREAM( <stream> ) . . . . . . start logging to a stream
*/
static Obj FuncINPUT_LOG_TO_STREAM(Obj self, Obj stream)
{
    RequireOutputStream(SELF_NAME, stream);
    if ( ! OpenInputLogStream(stream) ) {
        ErrorReturnVoid("InputLogTo: cannot log to stream", 0, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncCLOSE_OUTPUT_LOG_TO()  . . . . . . . . . . . . . . . . . stop logging
**
**  'FuncCLOSE_OUTPUT_LOG_TO' implements a method for 'OutputLogTo'.
**
**  'OutputLogTo()'
**
**  'OutputLogTo'  called with no argument  closes the current logfile again,
**  so that output from '*stdin*' and '*errin*' will no longer be echoed to a
**  file.
*/
static Obj FuncCLOSE_OUTPUT_LOG_TO(Obj self)
{
    if ( ! CloseOutputLog() ) {
        ErrorQuit("OutputLogTo: cannot close the logfile", 0, 0);
    }
    return True;
}


/****************************************************************************
**
*F  FuncOUTPUT_LOG_TO( <filename> )  . . . . . . . .  start logging to a file
**
**  'FuncOUTPUT_LOG_TO' implements a method for 'OutputLogTo'
**
**  'OutputLogTo( <filename> )'
**
**  'OutputLogTo' instructs GAP  to echo all  output from the standard output
**  files, '*stdin*' and '*errin*' to the file with the name <filename>.  The
**  file is created if it does not exist, otherwise it is truncated.
*/
static Obj FuncOUTPUT_LOG_TO(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);
    if ( ! OpenOutputLog( CONST_CSTR_STRING(filename) ) ) {
        ErrorReturnVoid("OutputLogTo: cannot log to %g", (Int)filename, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncOUTPUT_LOG_TO_STREAM( <stream> ) . . . . .  start logging to a stream
*/
static Obj FuncOUTPUT_LOG_TO_STREAM(Obj self, Obj stream)
{
    RequireOutputStream(SELF_NAME, stream);
    if ( ! OpenOutputLogStream(stream) ) {
        ErrorReturnVoid("OutputLogTo: cannot log to stream", 0, 0,
                        "you can 'return;'");
        return False;
    }
    return True;
}


/****************************************************************************
**
*F  FuncPrint( <self>, <args> ) . . . . . . . . . . . . . . . .  print <args>
*/
static Obj FuncPrint(Obj self, Obj args)
{
    volatile Obj        arg;
    volatile UInt       i;

    // print all the arguments, take care of strings and functions
    for ( i = 1;  i <= LEN_PLIST(args);  i++ ) {
        arg = ELM_LIST(args,i);
        if ( IS_PLIST(arg) && 0 < LEN_PLIST(arg) && IsStringConv(arg) ) {
            PrintString1(arg);
        }
        else if ( IS_STRING_REP(arg) ) {
            PrintString1(arg);
        }
        else {
            PrintObj( arg );
        }
    }

    return 0;
}

static Obj PRINT_OR_APPEND_TO_FILE_OR_STREAM(Obj args, int append, int file)
{
    const char * volatile funcname = append ? "AppendTo" : "PrintTo";
    volatile Obj        arg;
    volatile Obj        destination;
    volatile UInt       i;

    // first entry is the file or stream
    destination = ELM_LIST(args, 1);

    TypOutputFile output;

    // try to open the output and handle failures
    if (file) {
        RequireStringRep(funcname, destination);
        i = OpenOutput(&output, CONST_CSTR_STRING(destination), append);
        if (!i) {
            if (streq(CSTR_STRING(destination), "*errout*")) {
                Panic("Failed to open *errout*!");
            }
            ErrorQuit("%s: cannot open '%g' for output", (Int)funcname,
                      (Int)destination);
        }
    }
    else {
        if (CALL_1ARGS(IsOutputStream, destination) != True) {
            ErrorQuit("%s: <outstream> must be an output stream",
                      (Int)funcname, 0);
        }
        i = OpenOutputStream(&output, destination);
        if (!i) {
            ErrorQuit("%s: cannot open stream for output", (Int)funcname, 0);
        }
    }

    // print all the arguments, take care of strings and functions
    for ( i = 2;  i <= LEN_PLIST(args);  i++ ) {
        arg = ELM_LIST(args,i);

        // if an error occurs stop printing
        GAP_TRY
        {
            if (IS_PLIST(arg) && 0 < LEN_PLIST(arg) && IsStringConv(arg)) {
                PrintString1(arg);
            }
            else if (IS_STRING_REP(arg)) {
                PrintString1(arg);
            }
            else {
                PrintObj(arg);
            }
        }
        GAP_CATCH
        {
            CloseOutput(&output);
            GAP_THROW();
        }
    }

    // close the output file again, and return nothing
    if (!CloseOutput(&output)) {
        ErrorQuit("%s: cannot close output", (Int)funcname, 0);
    }

    return 0;
}
static Obj PRINT_OR_APPEND_TO(Obj args, int append)
{
    return PRINT_OR_APPEND_TO_FILE_OR_STREAM(args, append, 1);
}


static Obj PRINT_OR_APPEND_TO_STREAM(Obj args, int append)
{
    return PRINT_OR_APPEND_TO_FILE_OR_STREAM(args, append, 0);
}

/****************************************************************************
**
*F  FuncPRINT_TO( <self>, <args> )  . . . . . . . . . . . . . .  print <args>
*/
static Obj FuncPRINT_TO(Obj self, Obj args)
{
    return PRINT_OR_APPEND_TO(args, 0);
}


/****************************************************************************
**
*F  FuncPRINT_TO_STREAM( <self>, <args> ) . . . . . . . . . . .  print <args>
*/
static Obj FuncPRINT_TO_STREAM(Obj self, Obj args)
{
    /* Note that FuncPRINT_TO_STREAM and FuncAPPEND_TO_STREAM do exactly the
       same, they only differ in the function name they print as part
       of their error messages. */
    return PRINT_OR_APPEND_TO_STREAM(args, 0);
}


/****************************************************************************
**
*F  FuncAPPEND_TO( <self>, <args> ) . . . . . . . . . . . . . . append <args>
*/
static Obj FuncAPPEND_TO(Obj self, Obj args)
{
    return PRINT_OR_APPEND_TO(args, 1);
}


/****************************************************************************
**
*F  FuncAPPEND_TO_STREAM( <self>, <args> )  . . . . . . . . . . append <args>
*/
static Obj FuncAPPEND_TO_STREAM(Obj self, Obj args)
{
    /* Note that FuncPRINT_TO_STREAM and FuncAPPEND_TO_STREAM do exactly the
       same, they only differ in the function name they print as part
       of their error messages. */
    return PRINT_OR_APPEND_TO_STREAM(args, 1);
}


/****************************************************************************
**
*F  FuncREAD( <self>, <input> )  . . . . . . . . . . .  read a file or stream
**
**  Read the current input and close the input stream.
*/
static Obj FuncREAD(Obj self, Obj inputObj)
{
    TypInputFile input;
    if (!OpenInputFileOrStream(SELF_NAME, &input, inputObj))
        return False;

    GAP_TRY
    {
        // read the file
        READ_INNER(&input);
    }
    GAP_CATCH
    {
        CloseInput(&input);
        GAP_THROW();
    }

    if (!CloseInput(&input)) {
        ErrorQuit("Panic: READ cannot close input", 0, 0);
    }
    return True;
}


/****************************************************************************
**
*F  FuncREAD_STREAM_LOOP( <self>, <instream>, <outstream> ) . . read a stream
**
**  Read data from <instream> in a read-eval-print loop and write all output
**  to <outstream>. This is used by the GAP function `RunTests` and hence
**  indirectly for implementing `Test` and `TestDirectory`,
*/
static Obj FuncREAD_STREAM_LOOP(Obj self,
                                Obj instream,
                                Obj outstream,
                                Obj ctx)
{
    Int res;
    volatile Obj context = ctx;

    RequireInputStream(SELF_NAME, instream);
    RequireOutputStream(SELF_NAME, outstream);
    if (context == False)
        context = 0;
    else if (!IS_LVARS_OR_HVARS(context))
        RequireArgument(SELF_NAME, context,
                        "must be a local variables bag "
                        "or the value 'false'");

    TypInputFile input;
    if (!OpenInputStream(&input, instream, FALSE)) {
        return False;
    }

    TypOutputFile output;
    if (!OpenOutputStream(&output, outstream)) {
        res = CloseInput(&input);
        GAP_ASSERT(res);
        return False;
    }

    LockCurrentOutput(TRUE);

    // save the old print state
    volatile UInt oldPrintObjState = SetPrintObjState(0);

    BOOL rethrow = FALSE;

    GAP_TRY
    {
        // now do the reading
        while (1) {
            Obj  evalResult;
            BOOL dualSemicolon;
            UInt oldtime = SyTime();

            // read and evaluate the command
            SetPrintObjState(0);
            ExecStatus status =
                ReadEvalCommand(context, &input, &evalResult, &dualSemicolon);

            // stop the stopwatch
            UpdateTime(oldtime);

            // handle ordinary command
            if (status == STATUS_END && evalResult != 0) {
                UpdateLast(evalResult);
                if (!dualSemicolon) {
                    ViewObjHandler(evalResult);
                }
            }

            // handle return-value or return-void command
            else if (status == STATUS_RETURN) {
                Pr("'return' must not be used in file read-eval loop\n", 0, 0);
            }

            // handle quit command or <end-of-file>
            else if (status == STATUS_EOF || status == STATUS_QUIT ||
                     status == STATUS_QQUIT) {
                break;
            }
        }
    }
    GAP_CATCH
    {
        rethrow = TRUE;
    }

    SetPrintObjState(oldPrintObjState);

    LockCurrentOutput(FALSE);

    res = CloseInput(&input);
    res &= CloseOutput(&output);

    if (rethrow)
        GAP_THROW();

    return res ? True : False;
}


/****************************************************************************
**
*F  FuncREAD_AS_FUNC( <self>, <input> ) . read a file or stream as a function
*/
static Obj FuncREAD_AS_FUNC(Obj self, Obj inputObj)
{
    TypInputFile input;
    if (!OpenInputFileOrStream(SELF_NAME, &input, inputObj))
        return False;

    Obj func;

    GAP_TRY
    {
        func = READ_AS_FUNC(&input);
    }
    GAP_CATCH
    {
        CloseInput(&input);
        GAP_THROW();
    }

    if (!CloseInput(&input)) {
        ErrorQuit("Panic: READ_AS_FUNC cannot close input", 0, 0);
    }
    return func;
}


/****************************************************************************
**
*F  FuncREAD_GAP_ROOT( <self>, <filename> ) . . . . . . . . . . . read a file
*/
static Obj FuncREAD_GAP_ROOT(Obj self, Obj filename)
{
    Char filenamecpy[GAP_PATH_MAX];

    RequireStringRep(SELF_NAME, filename);

    // Copy to avoid garbage collection moving string
    gap_strlcpy(filenamecpy, CONST_CSTR_STRING(filename), GAP_PATH_MAX);
    // try to open the file
    return READ_GAP_ROOT(filenamecpy) ? True : False;
}


/****************************************************************************
**
*F  FuncTmpName( <self> ) . . . . . . . . . . . . . . return a temporary name
*/
static Obj FuncTmpName(Obj self)
{
    char name[100] = "/tmp/gaptempfile.XXXXXX";
#ifdef SYS_IS_CYGWIN32
    // If /tmp is missing, write into Window's temp directory
    DIR* dir = opendir("/tmp");
    if(dir) {
        closedir(dir);
    }
    else {
        strcpy(name, "C:/WINDOWS/Temp/gaptempfile.XXXXXX");
    }
#endif
    int fd = mkstemp(name);
    if (fd < 0)
        return Fail;
    close(fd);
    return MakeString(name);
}


/****************************************************************************
**
*F  FuncTmpDirectory( <self> )  . . . . . . . .  return a temporary directory
*/
static Obj FuncTmpDirectory(Obj self)
{
    Obj name = 0;
    char * env_tmpdir = getenv("TMPDIR");
    if (env_tmpdir != NULL) {
        name = MakeString(env_tmpdir);
    }
    else {
#ifdef SYS_IS_CYGWIN32
        // If /tmp is missing, write into Window's temp directory
        DIR* dir = opendir("/tmp");
        if(dir) {
            closedir(dir);
            name = MakeString("/tmp");
        }
        else {
            name = MakeString("C:/WINDOWS/Temp/");
        }
#else
        name = MakeString("/tmp");
#endif
    }
    const char * extra = "/gaptempdirXXXXXX";
    AppendCStr(name, extra, strlen(extra));

    if (mkdtemp(CSTR_STRING(name)) == 0)
        return Fail;
    return name;
}


/****************************************************************************
**
*F  FuncRemoveFile( <self>, <name> )  . . . . . . . . . .  remove file <name>
*/
static Obj FuncRemoveFile(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    return SyRemoveFile( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
}

/****************************************************************************
**
*F  FuncCreateDir( <self>, <name> )  . . . . . . . . . . . . create directory
*/
static Obj FuncCreateDir(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    return SyMkdir( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
}

/****************************************************************************
**
*F  FuncRemoveDir( <self>, <name> )  . . . . . . . . . . . . remove directory
*/
static Obj FuncRemoveDir(Obj self, Obj filename)
{
    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    return SyRmdir( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
}

/****************************************************************************
**
*F  FuncIS_DIR( <self>, <path> )  . . . . . check whether something is a dir
*/
static Obj FuncIS_DIR(Obj self, Obj path)
{
    RequireStringRep(SELF_NAME, path);

    // call the system dependent function
    return SyFileType(CONST_CSTR_STRING(path)) == 'D' ? True : False;
}

/****************************************************************************
**
*F  FuncGAP_getcwd( <self> ) . . . . . . . . . get working directory pathname
*/
static Obj FuncGAP_getcwd(Obj self)
{
    char * res;
    char   buf[GAP_PATH_MAX];

    res = getcwd(buf, sizeof(buf));
    if (res == NULL) {
        SySetErrorNo();
        return Fail;
    }
    return MakeImmString(buf);
}

/****************************************************************************
**
*F  FuncGAP_chdir( <self>, <path> ) . . . .  change current working directory
*/
static Obj FuncGAP_chdir(Obj self, Obj path)
{
    RequireStringRep(SELF_NAME, path);

    int res = chdir(CONST_CSTR_STRING(path));
    if (res < 0) {
        SySetErrorNo();
        return Fail;
    }
    return True;
}

/****************************************************************************
**
*F  FuncGAP_realpath( <self>, <path> ) . . . .  TODO
*/
static Obj FuncGAP_realpath(Obj self, Obj path)
{
    RequireStringRep(SELF_NAME, path);
    char resolved_path[GAP_PATH_MAX];

    if (NULL == realpath(CONST_CSTR_STRING(path), resolved_path)) {
        SySetErrorNo();
        return Fail;
    }
    return MakeString(resolved_path);
}


/****************************************************************************
**
*F * * * * * * * * * * * file access test functions * * * * * * * * * * * * *
*/


/****************************************************************************
**
*F  FuncLastSystemError( <self> ) .  . . . . . .  return the last system error
*/
static UInt ErrorMessageRNam;
static UInt ErrorNumberRNam;

static Obj FuncLastSystemError(Obj self)
{
    Obj             err;
    Obj             msg;

    // constructed an error record
    err = NEW_PREC(0);

    // check if an errors has occurred
    if ( SyLastErrorNo != 0 ) {
        ASS_REC( err, ErrorNumberRNam, INTOBJ_INT(SyLastErrorNo) );
        msg = MakeString(SyLastErrorMessage);
        ASS_REC( err, ErrorMessageRNam, msg );
    }

    // no error has occurred
    else {
        ASS_REC( err, ErrorNumberRNam, INTOBJ_INT(0) );
        msg = MakeString("no error");
        ASS_REC( err, ErrorMessageRNam, msg );
    }

    // return the error record
    return err;
}


/****************************************************************************
**
*F  FuncIsExistingFile( <self>, <name> )  . . . . . . does file <name> exists
*/
static Obj FuncIsExistingFile(Obj self, Obj filename)
{
    Int             res;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    res = SyIsExistingFile( CONST_CSTR_STRING(filename) );
    return res == -1 ? False : True;
}


/****************************************************************************
**
*F  FuncIsReadableFile( <self>, <name> )  . . . . . . is file <name> readable
*/
static Obj FuncIsReadableFile(Obj self, Obj filename)
{
    Int             res;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    res = SyIsReadableFile( CONST_CSTR_STRING(filename) );
    return res == -1 ? False : True;
}


/****************************************************************************
**
*F  FuncIsWritableFile( <self>, <name> )  . . . . . . is file <name> writable
*/
static Obj FuncIsWritableFile(Obj self, Obj filename)
{
    Int             res;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    res = SyIsWritableFile( CONST_CSTR_STRING(filename) );
    return res == -1 ? False : True;
}


/****************************************************************************
**
*F  FuncIsExecutableFile( <self>, <name> )  . . . . is file <name> executable
*/
static Obj FuncIsExecutableFile(Obj self, Obj filename)
{
    Int             res;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    res = SyIsExecutableFile( CONST_CSTR_STRING(filename) );
    return res == -1 ? False : True;
}


/****************************************************************************
**
*F  FuncIsDirectoryPath( <self>, <name> ) . . . .  is file <name> a directory
*/
static Obj FuncIsDirectoryPathString(Obj self, Obj filename)
{
    Int             res;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    res = SyIsDirectoryPath( CONST_CSTR_STRING(filename) );
    return res == -1 ? False : True;
}


/****************************************************************************
**
*F  FuncLIST_DIR( <self>, <dirname> ) . . . read names of files in dir
**
**  This function returns a GAP list which contains the names of all files
**  contained in a directory <dirname>.
**
**  If <dirname> could not be opened as a directory 'fail' is returned. The
**  reason for the error can be found with 'LastSystemError();' in GAP.
**
*/
static Obj FuncLIST_DIR(Obj self, Obj dirname)
{
    DIR *dir;
    struct dirent *entry;
    Obj res;

    RequireStringRep(SELF_NAME, dirname);

    SyClearErrorNo();
    dir = opendir(CONST_CSTR_STRING(dirname));
    if (dir == NULL) {
        SySetErrorNo();
        return Fail;
    }
    res = NEW_PLIST(T_PLIST, 16);
    while ((entry = readdir(dir))) {
        PushPlist(res, MakeImmString(entry->d_name));
    }
    closedir(dir);
    return res;
}

/****************************************************************************
**
*F * * * * * * * * * * * * text stream functions  * * * * * * * * * * * * * *
*/

/****************************************************************************
**
*F  FuncCLOSE_FILE( <self>, <fid> ) . . . . . . . . . . . . .  close a stream
*/
static Obj FuncCLOSE_FILE(Obj self, Obj fid)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);

    // call the system dependent function
    Int ret = SyFclose( ifid );
    return ret == -1 ? Fail : True;
}


/****************************************************************************
**
*F  FuncINPUT_TEXT_FILE( <self>, <name> ) . . . . . . . . . . . open a stream
*/
static Obj FuncINPUT_TEXT_FILE(Obj self, Obj filename)
{
    Int             fid;

    RequireStringRep(SELF_NAME, filename);

    // call the system dependent function
    SyClearErrorNo();
    fid = SyFopen(CONST_CSTR_STRING(filename), "r", TRUE);
    if (fid == -1)
        SySetErrorNo();
    return fid == -1 ? Fail : INTOBJ_INT(fid);
}


/****************************************************************************
**
*F  FuncIS_END_OF_FILE( <self>, <fid> ) . . . . . . . . . . .  is end of file
*/
static Obj FuncIS_END_OF_FILE(Obj self, Obj fid)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);

    Int ret = SyIsEndOfFile( ifid );
    return ret == -1 ? Fail : ( ret == 0 ? False : True );
}


/****************************************************************************
**
*F  FuncOUTPUT_TEXT_FILE( <self>, <name>, <append>, <comp> )  . open a stream
*/
static Obj FuncOUTPUT_TEXT_FILE(Obj self, Obj filename, Obj append, Obj comp)
{
    Int             fid;

    RequireStringRep(SELF_NAME, filename);
    RequireTrueOrFalse(SELF_NAME, append);
    RequireTrueOrFalse(SELF_NAME, comp);

    Int compbool = (comp == True);
    // call the system dependent function
    SyClearErrorNo();
    if ( append == True ) {
        fid = SyFopen(CONST_CSTR_STRING(filename), "a", compbool);
    }
    else {
        fid = SyFopen(CONST_CSTR_STRING(filename), "w", compbool);
    }
    if (fid == -1)
        SySetErrorNo();
    return fid == -1 ? Fail : INTOBJ_INT(fid);
}


/****************************************************************************
**
*F  FuncPOSITION_FILE( <self>, <fid> )  . . . . . . . . .  position of stream
*/
static Obj FuncPOSITION_FILE(Obj self, Obj fid)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);

    Int ret = SyFtell(ifid);

    // Return if failed
    if (ret == -1) {
        SySetErrorNo();
        return Fail;
    }

    return INTOBJ_INT(ret);
}



/****************************************************************************
**
*F  FuncREAD_BYTE_FILE( <self>, <fid> ) . . . . . . . . . . . . . read a byte
*/
static Obj FuncREAD_BYTE_FILE(Obj self, Obj fid)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);

    // call the system dependent function
    Int ret = SyGetch( ifid );

    return ret == EOF ? Fail : INTOBJ_INT(ret);
}


/****************************************************************************
**
*F  FuncREAD_LINE_FILE( <self>, <fid> ) . . . . . . . . . . . . . read a line
**
**  This uses fgets and works only if there are no zero characters in <fid>.
*/
static Obj FuncREAD_LINE_FILE(Obj self, Obj fid)
{
    Char            buf[256];
    Char *          cstr;
    Int             len, buflen;
    UInt            lstr;
    Obj             str;

    Int ifid = GetSmallInt(SELF_NAME, fid);

    // read <fid> until we see a newline or eof or we've read at least
    // one byte and more are not immediately available
    str = NEW_STRING(0);
    len = 0;
    while (1) {
      if ( len > 0 && !HasAvailableBytes(ifid))
        break;
      len += 255;
      GROW_STRING( str, len );
      if ( SyFgetsSemiBlock( buf, 256, ifid ) == 0 )
        break;
      buflen = strlen(buf);
      lstr = GET_LEN_STRING(str);
      cstr = CSTR_STRING(str) + lstr;
      memcpy( cstr, buf, buflen+1 );
      SET_LEN_STRING(str, lstr+buflen);
      if ( buf[buflen-1] == '\n' )
        break;
    }

    // fix the length of <str>
    len = GET_LEN_STRING(str);
    ResizeBag( str, SIZEBAG_STRINGLEN(len) );

    // and return
    return len == 0 ? Fail : str;
}

/****************************************************************************
**
*F  FuncREAD_ALL_FILE( <self>, <fid>, <limit> )  . . . . . . . read remainder
**
**  more precisely, read until either
**   (a) we have read at least one byte and no more are available
**   (b) we have evidence that it will never be possible to read a byte
**   (c) we have read <limit> bytes (-1 indicates no limit)
*/

static Obj FuncREAD_ALL_FILE(Obj self, Obj fid, Obj limit)
{
    Char            buf[20000];
    Int             len;
    // Length of string read this loop (or negative for error)
    UInt            lstr;
    Obj             str;
    UInt            csize;

    Int ifid = GetSmallInt(SELF_NAME, fid);
    Int ilim = GetSmallInt(SELF_NAME, limit);

    /* read <fid> until we see  eof or we've read at least
       one byte and more are not immediately available */
    str = NEW_STRING(0);
    len = 0;
    lstr = 0;

#ifdef SYS_IS_CYGWIN32
 getmore:
#endif
    while (ilim == -1 || len < ilim ) {
      lstr = 0;
      if ( len > 0 && !HasAvailableBytes(ifid))
          break;
      if (SyBufIsTTY(ifid)) {
          if (ilim == -1) {
              Pr("#W Warning -- reading to  end of input tty will never "
                 "end\n",
                 0, 0);
              csize = 20000;
          }
          else
              csize = ((ilim - len) > 20000) ? 20000 : ilim - len;

          if (SyFgetsSemiBlock(buf, csize, ifid))
              lstr = strlen(buf);
          else
              lstr = 0;
      }
      else {
          do {
              csize =
                  (ilim == -1 || (ilim - len) > 20000) ? 20000 : ilim - len;
              lstr = SyReadWithBuffer(ifid, buf, csize);
          } while (lstr == -1 && errno == EAGAIN);
      }
      if (lstr <= 0) {
          SyBufSetEOF(ifid);
          break;
      }
      GROW_STRING( str, len+lstr );
      memcpy(CHARS_STRING(str)+len, buf, lstr);
      len += lstr;
      SET_LEN_STRING(str, len);
    }

    // fix the length of <str>
    len = GET_LEN_STRING(str);
#ifdef SYS_IS_CYGWIN32
    // line end hackery
    UInt i = 0, j = 0;
    while (i < len) {
        if (CHARS_STRING(str)[i] == '\r') {
            if (i < len - 1 && CHARS_STRING(str)[i + 1] == '\n') {
                i++;
                continue;
            }
            else
                CHARS_STRING(str)[i] = '\n';
        }
        CHARS_STRING(str)[j++] = CHARS_STRING(str)[i++];
    }
    len = j;
    SET_LEN_STRING(str, len);
    // If we have not yet read enough, and have read at least one character this loop, then read more
    if (ilim != -1 && len < ilim && lstr > 0)
        goto getmore;
#endif
    ResizeBag( str, SIZEBAG_STRINGLEN(len) );

    // and return
    return len == 0 ? Fail : str;
}

/****************************************************************************
**
*F  FuncSEEK_POSITION_FILE( <self>, <fid>, <pos> )  . seek position of stream
*/
static Obj FuncSEEK_POSITION_FILE(Obj self, Obj fid, Obj pos)
{
    Int             ret;

    Int ifid = GetSmallInt(SELF_NAME, fid);
    Int ipos = GetSmallInt(SELF_NAME, pos);

    ret = SyFseek( ifid, ipos );
    return ret == -1 ? Fail : True;
}


/****************************************************************************
**
*F  FuncWRITE_BYTE_FILE( <self>, <fid>, <byte> )  . . . . . . .  write a byte
*/
static Obj FuncWRITE_BYTE_FILE(Obj self, Obj fid, Obj ch)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);
    Int ich = GetSmallInt(SELF_NAME, ch);

    // call the system dependent function
    Int ret = SyEchoch( ich, ifid );
    return ret == -1 ? Fail : True;
}

/****************************************************************************
**
*F  FuncWRITE_STRING_FILE_NC( <self>, <fid>, <string> ) .write a whole string
*/
static Obj FuncWRITE_STRING_FILE_NC(Obj self, Obj fid, Obj str)
{
    Int             len = 0, l, ret;
    const char      *ptr;

    // don't check the argument
    RequireStringRep(SELF_NAME, str);


    len = GET_LEN_STRING(str);
    ptr = CONST_CSTR_STRING(str);
    while (len > 0) {
      l = (len > 1048576) ? 1048576 : len;
      ret = SyWrite(INT_INTOBJ(fid), ptr, l);
      if (ret == -1) {
        SySetErrorNo();
        return Fail;
      }
      len -= ret;
      ptr += ret;
    }
    return True;
}

static Obj FuncREAD_STRING_FILE(Obj self, Obj fid)
{
    Int ifid = GetSmallInt(SELF_NAME, fid);
    return SyReadStringFid(ifid);
}

/****************************************************************************
**
*F  FuncFD_OF_FILE( <fid> )
*/
static Obj FuncFD_OF_FILE(Obj self, Obj fid)
{
    Int fd = GetSmallInt(SELF_NAME, fid);
    Int fdi = SyBufFileno(fd);
    return INTOBJ_INT(fdi);
}

#ifdef HPCGAP
static Obj FuncRAW_MODE_FILE(Obj self, Obj fid, Obj onoff)
{
    Int fd = GetSmallInt(SELF_NAME, fid);
    if (onoff == False || onoff == Fail) {
        syStopraw(fd);
        return False;
    }
    else
        return syStartraw(fd) ? True : False;
}
#endif

#ifdef HAVE_SELECT
static Obj FuncUNIXSelect(Obj self,
                          Obj inlist,
                          Obj outlist,
                          Obj exclist,
                          Obj timeoutsec,
                          Obj timeoutusec)
{
  fd_set infds,outfds,excfds;
  struct timeval tv;
  int n,maxfd;
  Int i,j;
  Obj o;

  RequirePlainList(SELF_NAME, inlist);
  RequirePlainList(SELF_NAME, outlist);
  RequirePlainList(SELF_NAME, exclist);

  FD_ZERO(&infds);
  FD_ZERO(&outfds);
  FD_ZERO(&excfds);
  maxfd = 0;
  // Handle input file descriptors:
  for (i = 1;i <= LEN_PLIST(inlist);i++) {
    o = ELM_PLIST(inlist,i);
    if (o != (Obj) 0 && IS_INTOBJ(o)) {
      j = INT_INTOBJ(o);  // a UNIX file descriptor
      FD_SET(j,&infds);
      if (j > maxfd) maxfd = j;
    }
  }
  // Handle output file descriptors:
  for (i = 1;i <= LEN_PLIST(outlist);i++) {
    o = ELM_PLIST(outlist,i);
    if (o != (Obj) 0 && IS_INTOBJ(o)) {
      j = INT_INTOBJ(o);  // a UNIX file descriptor
      FD_SET(j,&outfds);
      if (j > maxfd) maxfd = j;
    }
  }
  // Handle exception file descriptors:
  for (i = 1;i <= LEN_PLIST(exclist);i++) {
    o = ELM_PLIST(exclist,i);
    if (o != (Obj) 0 && IS_INTOBJ(o)) {
      j = INT_INTOBJ(o);  // a UNIX file descriptor
      FD_SET(j,&excfds);
      if (j > maxfd) maxfd = j;
    }
  }
  // Handle the timeout:
  if (timeoutsec != (Obj) 0 && IS_INTOBJ(timeoutsec) &&
      timeoutusec != (Obj) 0 && IS_INTOBJ(timeoutusec)) {
    tv.tv_sec = INT_INTOBJ(timeoutsec);
    tv.tv_usec = INT_INTOBJ(timeoutusec);
    n = select(maxfd+1,&infds,&outfds,&excfds,&tv);
  } else {
    n = select(maxfd+1,&infds,&outfds,&excfds,NULL);
  }

  if (n >= 0) {
    // Now run through the lists and call functions if ready:

    for (i = 1;i <= LEN_PLIST(inlist);i++) {
      o = ELM_PLIST(inlist,i);
      if (o != (Obj) 0 && IS_INTOBJ(o)) {
        j = INT_INTOBJ(o);  // a UNIX file descriptor
        if (!(FD_ISSET(j,&infds))) {
          SET_ELM_PLIST(inlist,i,Fail);
          CHANGED_BAG(inlist);
        }
      }
    }
    // Handle output file descriptors:
    for (i = 1;i <= LEN_PLIST(outlist);i++) {
      o = ELM_PLIST(outlist,i);
      if (o != (Obj) 0 && IS_INTOBJ(o)) {
        j = INT_INTOBJ(o);  // a UNIX file descriptor
        if (!(FD_ISSET(j,&outfds))) {
          SET_ELM_PLIST(outlist,i,Fail);
          CHANGED_BAG(outlist);
        }
      }
    }
    // Handle exception file descriptors:
    for (i = 1;i <= LEN_PLIST(exclist);i++) {
      o = ELM_PLIST(exclist,i);
      if (o != (Obj) 0 && IS_INTOBJ(o)) {
        j = INT_INTOBJ(o);  // a UNIX file descriptor
        if (!(FD_ISSET(j,&excfds))) {
          SET_ELM_PLIST(exclist,i,Fail);
          CHANGED_BAG(exclist);
        }
      }
    }
  }
  return INTOBJ_INT(n);
}
#endif


/****************************************************************************
**
*F * * * * * * * * * * * * * initialize module * * * * * * * * * * * * * * *
*/

/****************************************************************************
**
*V  GVarFuncs . . . . . . . . . . . . . . . . . . list of functions to export
*/
static StructGVarFunc GVarFuncs[] = {

    GVAR_FUNC_1ARGS(READ, input),
    GVAR_FUNC_4ARGS(
        READ_ALL_COMMANDS, instream, echo, capture, resultCallback),
    GVAR_FUNC_2ARGS(READ_COMMAND_REAL, stream, echo),
    GVAR_FUNC_3ARGS(READ_STREAM_LOOP, stream, catchstderrout, context),
    GVAR_FUNC_1ARGS(READ_AS_FUNC, input),
    GVAR_FUNC_1ARGS(READ_GAP_ROOT, filename),
    GVAR_FUNC_3ARGS(CALL_WITH_STREAM, stream, func, args),
    GVAR_FUNC_1ARGS(LOG_TO, filename),
    GVAR_FUNC_1ARGS(LOG_TO_STREAM, filename),
    GVAR_FUNC_0ARGS(CLOSE_LOG_TO),
    GVAR_FUNC_1ARGS(INPUT_LOG_TO, filename),
    GVAR_FUNC_1ARGS(INPUT_LOG_TO_STREAM, filename),
    GVAR_FUNC_0ARGS(CLOSE_INPUT_LOG_TO),
    GVAR_FUNC_1ARGS(OUTPUT_LOG_TO, filename),
    GVAR_FUNC_1ARGS(OUTPUT_LOG_TO_STREAM, filename),
    GVAR_FUNC_0ARGS(CLOSE_OUTPUT_LOG_TO),
    GVAR_FUNC_XARGS(Print, -1, "arg"),
    GVAR_FUNC_XARGS(PRINT_TO, -1, "arg"),
    GVAR_FUNC_XARGS(PRINT_TO_STREAM, -1, "arg"),
    GVAR_FUNC_XARGS(APPEND_TO, -1, "arg"),
    GVAR_FUNC_XARGS(APPEND_TO_STREAM, -1, "arg"),
    GVAR_FUNC_0ARGS(TmpName),
    GVAR_FUNC_0ARGS(TmpDirectory),
    GVAR_FUNC_1ARGS(RemoveFile, filename),
    GVAR_FUNC_1ARGS(CreateDir, filename),
    GVAR_FUNC_1ARGS(RemoveDir, filename),
    GVAR_FUNC_1ARGS(IS_DIR, path),
    GVAR_FUNC_0ARGS(GAP_getcwd),
    GVAR_FUNC_1ARGS(GAP_chdir, path),
    GVAR_FUNC_1ARGS(GAP_realpath, path),
    GVAR_FUNC_0ARGS(LastSystemError),
    GVAR_FUNC_1ARGS(IsExistingFile, filename),
    GVAR_FUNC_1ARGS(IsReadableFile, filename),
    GVAR_FUNC_1ARGS(IsWritableFile, filename),
    GVAR_FUNC_1ARGS(IsExecutableFile, filename),
    GVAR_FUNC_1ARGS(IsDirectoryPathString, filename),
    GVAR_FUNC_1ARGS(LIST_DIR, dirname),
    GVAR_FUNC_1ARGS(CLOSE_FILE, fid),
    GVAR_FUNC_1ARGS(INPUT_TEXT_FILE, filename),
    GVAR_FUNC_3ARGS(OUTPUT_TEXT_FILE, filename, append, compress),
    GVAR_FUNC_1ARGS(IS_END_OF_FILE, fid),
    GVAR_FUNC_1ARGS(POSITION_FILE, fid),
    GVAR_FUNC_1ARGS(READ_BYTE_FILE, fid),
    GVAR_FUNC_1ARGS(READ_LINE_FILE, fid),
    GVAR_FUNC_2ARGS(READ_ALL_FILE, fid, limit),
    GVAR_FUNC_2ARGS(SEEK_POSITION_FILE, fid, pos),
    GVAR_FUNC_2ARGS(WRITE_BYTE_FILE, fid, byte),
    GVAR_FUNC_2ARGS(WRITE_STRING_FILE_NC, fid, string),
    GVAR_FUNC_1ARGS(READ_STRING_FILE, fid),
    GVAR_FUNC_1ARGS(FD_OF_FILE, fid),
#ifdef HPCGAP
    GVAR_FUNC_2ARGS(RAW_MODE_FILE, fid, onoff),
#endif
#ifdef HAVE_SELECT
    GVAR_FUNC_5ARGS(
        UNIXSelect, inlist, outlist, exclist, timeoutsec, timeoutusec),
#endif
    { 0, 0, 0, 0, 0 }

};


/****************************************************************************
**
*F  InitKernel( <module> )  . . . . . . . . initialise kernel data structures
*/
static Int InitKernel (
    StructInitInfo *    module )
{
    // init filters and functions
    InitHdlrFuncsFromTable( GVarFuncs );

    ImportFuncFromLibrary( "IsInputStream", &IsInputStream );
    ImportFuncFromLibrary( "IsOutputStream", &IsOutputStream );

    return 0;
}


/****************************************************************************
**
*F  PostRestore( <module> ) . . . . . . . . . . . . . after restore workspace
*/
static Int PostRestore (
    StructInitInfo *    module )
{
    // file access test functions
    ErrorNumberRNam  = RNamName("number");
    ErrorMessageRNam = RNamName("message");

    // pick up the number of this global
    LastReadValueGVar = GVarName("LastReadValue");

    return 0;
}


/****************************************************************************
**
*F  InitLibrary( <module> ) . . . . . . .  initialise library data structures
*/
static Int InitLibrary (
    StructInitInfo *    module )
{
    // init filters and functions
    InitGVarFuncsFromTable( GVarFuncs );


    return PostRestore( module );
}


/****************************************************************************
**
*F  InitInfoStreams() . . . . . . . . . . . . . . . . table of init functions
*/
static StructInitInfo module = {
    // init struct using C99 designated initializers; for a full list of
    // fields, please refer to the definition of StructInitInfo
    .type = MODULE_BUILTIN,
    .name = "streams",
    .initKernel = InitKernel,
    .initLibrary = InitLibrary,
    .postRestore = PostRestore,
};

StructInitInfo * InitInfoStreams ( void )
{
    return &module;
}
