1 /****************************************************************************
2 **
3 **  This file is part of GAP, a system for computational discrete algebra.
4 **
5 **  Copyright of GAP belongs to its developers, whose names are too numerous
6 **  to list here. Please refer to the COPYRIGHT file for details.
7 **
8 **  SPDX-License-Identifier: GPL-2.0-or-later
9 **
10 **  This file contains the  various read-eval-print loops and streams related
11 **  stuff.  The system depend part is in "sysfiles.c".
12 */
13 
14 #include "streams.h"
15 
16 #include "bool.h"
17 #include "calls.h"
18 #include "error.h"
19 #include "funcs.h"
20 #include "gap.h"
21 #include "gapstate.h"
22 #include "gvars.h"
23 #include "integer.h"
24 #include "io.h"
25 #include "lists.h"
26 #include "modules.h"
27 #include "opers.h"
28 #include "plist.h"
29 #include "precord.h"
30 #include "read.h"
31 #include "records.h"
32 #include "stats.h"
33 #include "stringobj.h"
34 #include "sysfiles.h"
35 #include "sysopt.h"
36 #include "vars.h"
37 
38 #include <dirent.h>
39 #include <errno.h>
40 #include <time.h>
41 #include <unistd.h>
42 
43 #ifdef HAVE_SELECT
44 // For FuncUNIXSelect
45 #include <sys/time.h>
46 #endif
47 
48 
49 static Obj IsInputStream;
50 static Obj IsOutputStream;
51 
52 #define RequireInputStream(funcname, op)                                     \
53     RequireArgumentCondition(funcname, op,                                   \
54                              CALL_1ARGS(IsInputStream, op) == True,          \
55                              "must be an input stream")
56 
57 #define RequireOutputStream(funcname, op)                                    \
58     RequireArgumentCondition(funcname, op,                                   \
59                              CALL_1ARGS(IsOutputStream, op) == True,         \
60                              "must be an output stream")
61 
62 
63 /****************************************************************************
64 **
65 *F * * * * * * * * * streams and files related functions  * * * * * * * * * *
66 */
67 
READ_COMMAND(Obj * evalResult)68 static Int READ_COMMAND(Obj *evalResult)
69 {
70     ExecStatus    status;
71 
72     ClearError();
73     status = ReadEvalCommand(STATE(BottomLVars), evalResult, 0);
74     if( status == STATUS_EOF )
75         return 0;
76 
77     if ( STATE(UserHasQuit) || STATE(UserHasQUIT) )
78         return 0;
79 
80     /* handle return-value or return-void command                          */
81     if ( status & (STATUS_RETURN_VAL | STATUS_RETURN_VOID) ) {
82         Pr( "'return' must not be used in file read-eval loop\n", 0L, 0L );
83     }
84 
85     /* handle quit command                                 */
86     else if (status == STATUS_QUIT) {
87         SetRecursionDepth(0);
88         STATE(UserHasQuit) = 1;
89     }
90     else if (status == STATUS_QQUIT) {
91         STATE(UserHasQUIT) = 1;
92     }
93     ClearError();
94 
95     return 1;
96 }
97 
98 /****************************************************************************
99 **
100 *F  FuncREAD_ALL_COMMANDS( <self>, <instream>, <echo>, <capture>, <outputFunc> )
101 **
102 **  FuncREAD_ALL_COMMANDS attempts to execute all statements read from the
103 **  stream <instream>. It returns 'fail' if the stream cannot be opened,
104 **  otherwise a list of lists, each entry of which reflects the result of the
105 **  execution of one statement.
106 **
107 **  If the parameter <echo> is 'true', then the statements are echoed to the
108 **  current output.
109 **
110 **  If the parameter <capture> is 'true', then any output occurring during
111 **  execution of a statement, including the output of <outputFunc>, is
112 **  captured into a string.
113 **
114 **  If <resultCallback> is a function, then this function is called on every
115 **  statement result, otherwise this parameter is ignored. Possible outputs of
116 **  this function are captured if <capture> is 'true'.
117 **
118 **  The results are returned as lists of length at most five, the structure of
119 **  which is explained below:
120 **
121 **  - The first entry is 'true' if the statement was executed successfully,
122 **    and 'false' otherwise.
123 **
124 **  - If the first entry is 'true', then the second entry is bound to the
125 **    result of the statement if there was one, and unbound otherwise.
126 **
127 **  - The third entry is 'true' if the statement ended in a dual semicolon,
128 **    and 'false' otherwise.
129 **
130 **  - The fourth entry contains the return value of <resultCallback> if
131 **    applicable.
132 **
133 **  - The fifth entry contains the captured output as a string, if <capture>
134 **    is 'true'.
135 **
136 **  This function is currently used in interactive tools such as the GAP
137 **  Jupyter kernel to execute cells and is likely to be replaced by a function
138 **  that can read a single command from a stream without losing the rest of
139 **  its content.
140 */
READ_ALL_COMMANDS(Obj instream,Obj echo,Obj capture,Obj resultCallback)141 Obj READ_ALL_COMMANDS(Obj instream, Obj echo, Obj capture, Obj resultCallback)
142 {
143     ExecStatus status;
144     UInt       dualSemicolon;
145     Obj        result, resultList;
146     Obj        copy;
147     Obj        evalResult;
148     Obj        outstream = 0;
149     Obj        outstreamString = 0;
150 
151     RequireInputStream("READ_ALL_COMMANDS", instream);
152 
153     /* try to open the streams */
154     if (!OpenInputStream(instream, echo == True)) {
155         return Fail;
156     }
157 
158 
159     if (capture == True) {
160         outstreamString = NEW_STRING(0);
161         outstream = DoOperation2Args(ValGVar(GVarName("OutputTextString")),
162                                      outstreamString, True);
163     }
164     if (outstream && !OpenOutputStream(outstream)) {
165         CloseInput();
166         return Fail;
167     }
168 
169     resultList = NEW_PLIST(T_PLIST, 16);
170 
171     do {
172         ClearError();
173         if (outstream) {
174             // Clean in case there has been any output
175             SET_LEN_STRING(outstreamString, 0);
176         }
177 
178         status =
179             ReadEvalCommand(STATE(BottomLVars), &evalResult, &dualSemicolon);
180 
181         if (!(status & (STATUS_EOF | STATUS_QUIT | STATUS_QQUIT))) {
182             result = NEW_PLIST(T_PLIST, 5);
183             AssPlist(result, 1, False);
184             PushPlist(resultList, result);
185 
186             if (!(status & STATUS_ERROR)) {
187 
188                 AssPlist(result, 1, True);
189                 AssPlist(result, 3, dualSemicolon ? True : False);
190 
191                 if (evalResult) {
192                     AssPlist(result, 2, evalResult);
193                 }
194 
195                 if (evalResult && IS_FUNC(resultCallback) && !dualSemicolon) {
196                     Obj tmp = CALL_1ARGS(resultCallback, evalResult);
197                     AssPlist(result, 4, tmp);
198                 }
199             }
200             // Capture output
201             if (capture == True) {
202                 // Flush output
203                 Pr("\03", 0L, 0L);
204                 copy = CopyToStringRep(outstreamString);
205                 SET_LEN_STRING(outstreamString, 0);
206                 AssPlist(result, 5, copy);
207             }
208         }
209     } while (!(status & (STATUS_EOF | STATUS_QUIT | STATUS_QQUIT)));
210 
211     if (outstream)
212         CloseOutput();
213     CloseInput();
214     ClearError();
215 
216     return resultList;
217 }
218 
FuncREAD_ALL_COMMANDS(Obj self,Obj instream,Obj echo,Obj capture,Obj resultCallback)219 static Obj FuncREAD_ALL_COMMANDS(
220     Obj self, Obj instream, Obj echo, Obj capture, Obj resultCallback)
221 {
222     return READ_ALL_COMMANDS(instream, echo, capture, resultCallback);
223 }
224 
225 
226 /*
227  Returns a list with one or two entries. The first
228  entry is set to "false" if there was any error
229  executing the command, and "true" otherwise.
230  The second entry, if present, is the return value of
231  the command. If it not present, the command returned nothing.
232 */
FuncREAD_COMMAND_REAL(Obj self,Obj stream,Obj echo)233 static Obj FuncREAD_COMMAND_REAL(Obj self, Obj stream, Obj echo)
234 {
235     Int status;
236     Obj result;
237     Obj evalResult;
238 
239     RequireInputStream("READ_COMMAND_REAL", stream);
240 
241     result = NEW_PLIST( T_PLIST, 2 );
242     SET_LEN_PLIST(result, 1);
243     SET_ELM_PLIST(result, 1, False);
244 
245     /* try to open the file                                                */
246     if (!OpenInputStream(stream, echo == True)) {
247         return result;
248     }
249 
250     status = READ_COMMAND(&evalResult);
251 
252     CloseInput();
253 
254     if( status == 0 ) return result;
255 
256     if (STATE(UserHasQUIT)) {
257       STATE(UserHasQUIT) = 0;
258       return result;
259     }
260 
261     if (STATE(UserHasQuit)) {
262       STATE(UserHasQuit) = 0;
263     }
264 
265     SET_ELM_PLIST(result, 1, True);
266     if (evalResult) {
267         SET_LEN_PLIST(result, 2);
268         SET_ELM_PLIST(result, 2, evalResult);
269     }
270     return result;
271 }
272 
273 /****************************************************************************
274 **
275 *F  READ()  . . . . . . . . . . . . . . . . . . . . . . .  read current input
276 **
277 **  Read the current input and close the input stream.
278 */
279 
280 static UInt LastReadValueGVar;
281 
READ_INNER(UInt UseUHQ)282 static Int READ_INNER ( UInt UseUHQ )
283 {
284     if (STATE(UserHasQuit))
285       {
286         Pr("Warning: Entering READ with UserHasQuit set, this should never happen, resetting",0,0);
287         STATE(UserHasQuit) = 0;
288       }
289     if (STATE(UserHasQUIT))
290       {
291         Pr("Warning: Entering READ with UserHasQUIT set, this should never happen, resetting",0,0);
292         STATE(UserHasQUIT) = 0;
293       }
294     MakeReadWriteGVar(LastReadValueGVar);
295     AssGVar( LastReadValueGVar, 0);
296     MakeReadOnlyGVar(LastReadValueGVar);
297     /* now do the reading                                                  */
298     while ( 1 ) {
299         ClearError();
300         Obj evalResult;
301         ExecStatus status = ReadEvalCommand(STATE(BottomLVars), &evalResult, 0);
302         if (STATE(UserHasQuit) || STATE(UserHasQUIT))
303             break;
304 
305         /* handle return-value or return-void command                      */
306         if ( status & (STATUS_RETURN_VAL | STATUS_RETURN_VOID) ) {
307             Pr(
308                 "'return' must not be used in file read-eval loop\n",
309                 0L, 0L );
310         }
311 
312         /* handle quit command or <end-of-file>                            */
313         else if ( status  & (STATUS_ERROR | STATUS_EOF))
314           break;
315         else if (status == STATUS_QUIT) {
316           SetRecursionDepth(0);
317           STATE(UserHasQuit) = 1;
318           break;
319         }
320         else if (status == STATUS_QQUIT) {
321           STATE(UserHasQUIT) = 1;
322           break;
323         }
324         if (evalResult)
325           {
326             MakeReadWriteGVar(LastReadValueGVar);
327             AssGVar( LastReadValueGVar, evalResult);
328             MakeReadOnlyGVar(LastReadValueGVar);
329           }
330 
331     }
332 
333 
334     /* close the input file again, and return 'true'                       */
335     if ( ! CloseInput() ) {
336         ErrorQuit(
337             "Panic: READ cannot close input, this should not happen",
338             0L, 0L );
339     }
340     ClearError();
341 
342     if (!UseUHQ && STATE(UserHasQuit)) {
343       STATE(UserHasQuit) = 0; /* stop recovery here */
344       return 2;
345     }
346 
347     return 1;
348 }
349 
350 
READ(void)351 static Int READ( void ) {
352   return READ_INNER(1);
353 }
354 
READ_NORECOVERY(void)355 static Int READ_NORECOVERY( void ) {
356   return READ_INNER(0);
357 }
358 
359 /****************************************************************************
360 **
361 *F  READ_AS_FUNC()  . . . . . . . . . . . . .  read current input as function
362 **
363 **  Read the current input as function and close the input stream.
364 */
READ_AS_FUNC(void)365 Obj READ_AS_FUNC ( void )
366 {
367     /* now do the reading                                                  */
368     ClearError();
369     Obj evalResult;
370     UInt type = ReadEvalFile(&evalResult);
371 
372     /* get the function                                                    */
373     Obj func = (type == 0) ? evalResult : Fail;
374 
375     /* close the input file again, and return 'true'                       */
376     if ( ! CloseInput() ) {
377         ErrorQuit(
378             "Panic: READ_AS_FUNC cannot close input, this should not happen",
379             0L, 0L );
380     }
381     ClearError();
382 
383     /* return the function                                                 */
384     return func;
385 }
386 
387 
READ_TEST_OR_LOOP(Obj context)388 static void READ_TEST_OR_LOOP(Obj context)
389 {
390     UInt                type;
391     UInt                oldtime;
392     UInt                dualSemicolon;
393 
394     /* get the starting time                                               */
395     oldtime = SyTime();
396 
397     /* now do the reading                                                  */
398     while ( 1 ) {
399 
400         /* read and evaluate the command                                   */
401         ClearError();
402         Obj evalResult;
403         type = ReadEvalCommand(context, &evalResult, &dualSemicolon);
404 
405         /* stop the stopwatch                                              */
406         AssGVarWithoutReadOnlyCheck(Time, ObjInt_Int(SyTime() - oldtime));
407 
408         /* handle ordinary command                                         */
409         if ( type == 0 && evalResult != 0 ) {
410 
411             /* remember the value in 'last' and the time in 'time'         */
412             UpdateLast(evalResult, 3);
413 
414             /* print the result                                            */
415             if ( ! dualSemicolon ) {
416                 Bag currLVars = STATE(CurrLVars); /* in case view runs into error */
417                 ViewObjHandler( evalResult );
418                 SWITCH_TO_OLD_LVARS(currLVars);
419             }
420         }
421 
422         /* handle return-value or return-void command                      */
423         else if ( type & (STATUS_RETURN_VAL | STATUS_RETURN_VOID) ) {
424             Pr( "'return' must not be used in file read-eval loop\n",
425                 0L, 0L );
426         }
427 
428         /* handle quit command or <end-of-file>                            */
429         else if ( type & (STATUS_QUIT | STATUS_EOF) ) {
430             break;
431         }
432         // FIXME: what about other types? e.g. STATUS_ERROR and STATUS_QQUIT
433 
434     }
435 
436     ClearError();
437 }
438 
439 
440 /****************************************************************************
441 **
442 *F  READ_GAP_ROOT( <filename> ) . . .  read from gap root, dyn-load or static
443 **
444 **  'READ_GAP_ROOT' tries to find  a file under  the root directory,  it will
445 **  search all   directories given   in 'SyGapRootPaths',  check  dynamically
446 **  loadable modules and statically linked modules.
447 */
READ_GAP_ROOT(const Char * filename)448 Int READ_GAP_ROOT ( const Char * filename )
449 {
450     TypGRF_Data         result;
451     Int                 res;
452     UInt                type;
453 
454     /* try to find the file                                                */
455     res = SyFindOrLinkGapRootFile( filename, &result );
456 
457     /* not found                                                           */
458     if ( res == 0 ) {
459         return 0;
460     }
461 
462     // statically linked
463     else if (res == 2) {
464         // This code section covers transparently loading GAC compiled
465         // versions of GAP source files, by running code similar to that in
466         // FuncLOAD_STAT. For example, lib/oper1.g is compiled into C code
467         // which is stored in src/c_oper1.c; when reading lib/oper1.g, we
468         // instead will load its compiled version.
469         if ( SyDebugLoading ) {
470             Pr("#I  READ_GAP_ROOT: loading '%s' statically\n", (Int)filename,
471                0);
472         }
473         ActivateModule(result.module_info);
474         RecordLoadedModule(result.module_info, 1, filename);
475         return 1;
476     }
477 
478     /* special handling for the other cases, if we are trying to load compiled
479        modules needed for a saved workspace ErrorQuit is not available */
480     else if (SyRestoring) {
481         if ( res == 3 ) {
482             Pr("Can't find compiled module '%s' needed by saved workspace\n",
483                (Int) filename, 0L);
484             return 0;
485         }
486         Pr("unknown result code %d from 'SyFindGapRoot'", res, 0L );
487         SyExit(1);
488     }
489 
490     /* ordinary gap file                                                   */
491     else if ( res == 3 ) {
492         if ( SyDebugLoading ) {
493             Pr( "#I  READ_GAP_ROOT: loading '%s' as GAP file\n",
494                 (Int)filename, 0L );
495         }
496         if ( OpenInput(result.path) ) {
497             while ( 1 ) {
498                 ClearError();
499                 Obj evalResult;
500                 type = ReadEvalCommand(STATE(BottomLVars), &evalResult, 0);
501                 if (STATE(UserHasQuit) || STATE(UserHasQUIT))
502                   break;
503                 if ( type & (STATUS_RETURN_VAL | STATUS_RETURN_VOID) ) {
504                     Pr( "'return' must not be used in file", 0L, 0L );
505                 }
506                 else if ( type & (STATUS_QUIT | STATUS_EOF) ) {
507                     break;
508                 }
509             }
510             CloseInput();
511             ClearError();
512             return 1;
513         }
514     }
515 
516     /* don't know                                                          */
517     else {
518         ErrorQuit( "unknown result code %d from 'SyFindGapRoot'", res, 0L );
519     }
520     return 0;
521 }
522 
523 
524 /****************************************************************************
525 **
526 *F  FuncCLOSE_LOG_TO()  . . . . . . . . . . . . . . . . . . . .  stop logging
527 **
528 **  'FuncCLOSE_LOG_TO' implements a method for 'LogTo'.
529 **
530 **  'LogTo()'
531 **
532 **  'LogTo' called with no argument closes the current logfile again, so that
533 **  input   from  '*stdin*'  and  '*errin*'  and  output  to  '*stdout*'  and
534 **  '*errout*' will no longer be echoed to a file.
535 */
FuncCLOSE_LOG_TO(Obj self)536 static Obj FuncCLOSE_LOG_TO(Obj self)
537 {
538     if ( ! CloseLog() ) {
539         ErrorQuit("LogTo: can not close the logfile",0L,0L);
540     }
541     return True;
542 }
543 
544 
545 /****************************************************************************
546 **
547 *F  FuncLOG_TO( <filename> ) . . . . . . . . . . . .  start logging to a file
548 **
549 **  'FuncLOG_TO' implements a method for 'LogTo'
550 **
551 **  'LogTo( <filename> )'
552 **
553 **  'LogTo' instructs GAP to echo all input from the  standard  input  files,
554 **  '*stdin*' and '*errin*' and all output  to  the  standard  output  files,
555 **  '*stdout*'  and  '*errout*',  to  the  file  with  the  name  <filename>.
556 **  The file is created if it does not  exist,  otherwise  it  is  truncated.
557 */
FuncLOG_TO(Obj self,Obj filename)558 static Obj FuncLOG_TO(Obj self, Obj filename)
559 {
560     RequireStringRep("LogTo", filename);
561     if ( ! OpenLog( CONST_CSTR_STRING(filename) ) ) {
562         ErrorReturnVoid( "LogTo: cannot log to %g",
563                          (Int)filename, 0L,
564                          "you can 'return;'" );
565         return False;
566     }
567     return True;
568 }
569 
570 
571 /****************************************************************************
572 **
573 *F  FuncLOG_TO_STREAM( <stream> ) . . . . . . . . . start logging to a stream
574 */
FuncLOG_TO_STREAM(Obj self,Obj stream)575 static Obj FuncLOG_TO_STREAM(Obj self, Obj stream)
576 {
577     RequireOutputStream("LogTo", stream);
578     if ( ! OpenLogStream(stream) ) {
579         ErrorReturnVoid( "LogTo: cannot log to stream", 0L, 0L,
580                          "you can 'return;'" );
581         return False;
582     }
583     return True;
584 }
585 
586 
587 /****************************************************************************
588 **
589 *F  FuncCLOSE_INPUT_LOG_TO()  . . . . . . . . . . . . . . . . .  stop logging
590 **
591 **  'FuncCLOSE_INPUT_LOG_TO' implements a method for 'InputLogTo'.
592 **
593 **  'InputLogTo()'
594 **
595 **  'InputLogTo' called with no argument closes the current logfile again, so
596 **  that input from  '*stdin*' and '*errin*' will   no longer be  echoed to a
597 **  file.
598 */
FuncCLOSE_INPUT_LOG_TO(Obj self)599 static Obj FuncCLOSE_INPUT_LOG_TO(Obj self)
600 {
601     if ( ! CloseInputLog() ) {
602         ErrorQuit("InputLogTo: can not close the logfile",0L,0L);
603     }
604     return True;
605 }
606 
607 
608 /****************************************************************************
609 **
610 *F  FuncINPUT_LOG_TO( <filename> )  . . . . . . . . . start logging to a file
611 **
612 **  'FuncINPUT_LOG_TO' implements a method for 'InputLogTo'
613 **
614 **  'InputLogTo( <filename> )'
615 **
616 **  'InputLogTo'  instructs  GAP to echo   all input from  the standard input
617 **  files, '*stdin*' and '*errin*' to the file with the name <filename>.  The
618 **  file is created if it does not exist, otherwise it is truncated.
619 */
FuncINPUT_LOG_TO(Obj self,Obj filename)620 static Obj FuncINPUT_LOG_TO(Obj self, Obj filename)
621 {
622     RequireStringRep("InputLogTo", filename);
623     if ( ! OpenInputLog( CONST_CSTR_STRING(filename) ) ) {
624         ErrorReturnVoid( "InputLogTo: cannot log to %g",
625                          (Int)filename, 0L,
626                          "you can 'return;'" );
627         return False;
628     }
629     return True;
630 }
631 
632 
633 /****************************************************************************
634 **
635 *F  FuncINPUT_LOG_TO_STREAM( <stream> ) . . . . . . start logging to a stream
636 */
FuncINPUT_LOG_TO_STREAM(Obj self,Obj stream)637 static Obj FuncINPUT_LOG_TO_STREAM(Obj self, Obj stream)
638 {
639     RequireOutputStream("InputLogTo", stream);
640     if ( ! OpenInputLogStream(stream) ) {
641         ErrorReturnVoid( "InputLogTo: cannot log to stream", 0L, 0L,
642                          "you can 'return;'" );
643         return False;
644     }
645     return True;
646 }
647 
648 
649 /****************************************************************************
650 **
651 *F  FuncCLOSE_OUTPUT_LOG_TO()  . . . . . . . . . . . . . . . . . stop logging
652 **
653 **  'FuncCLOSE_OUTPUT_LOG_TO' implements a method for 'OutputLogTo'.
654 **
655 **  'OutputLogTo()'
656 **
657 **  'OutputLogTo'  called with no argument  closes the current logfile again,
658 **  so that output from '*stdin*' and '*errin*' will no longer be echoed to a
659 **  file.
660 */
FuncCLOSE_OUTPUT_LOG_TO(Obj self)661 static Obj FuncCLOSE_OUTPUT_LOG_TO(Obj self)
662 {
663     if ( ! CloseOutputLog() ) {
664         ErrorQuit("OutputLogTo: can not close the logfile",0L,0L);
665     }
666     return True;
667 }
668 
669 
670 /****************************************************************************
671 **
672 *F  FuncOUTPUT_LOG_TO( <filename> )  . . . . . . . .  start logging to a file
673 **
674 **  'FuncOUTPUT_LOG_TO' implements a method for 'OutputLogTo'
675 **
676 **  'OutputLogTo( <filename> )'
677 **
678 **  'OutputLogTo' instructs GAP  to echo all  output from the standard output
679 **  files, '*stdin*' and '*errin*' to the file with the name <filename>.  The
680 **  file is created if it does not exist, otherwise it is truncated.
681 */
FuncOUTPUT_LOG_TO(Obj self,Obj filename)682 static Obj FuncOUTPUT_LOG_TO(Obj self, Obj filename)
683 {
684     RequireStringRep("OutputLogTo", filename);
685     if ( ! OpenOutputLog( CONST_CSTR_STRING(filename) ) ) {
686         ErrorReturnVoid( "OutputLogTo: cannot log to %g",
687                          (Int)filename, 0L,
688                          "you can 'return;'" );
689         return False;
690     }
691     return True;
692 }
693 
694 
695 /****************************************************************************
696 **
697 *F  FuncOUTPUT_LOG_TO_STREAM( <stream> ) . . . . .  start logging to a stream
698 */
FuncOUTPUT_LOG_TO_STREAM(Obj self,Obj stream)699 static Obj FuncOUTPUT_LOG_TO_STREAM(Obj self, Obj stream)
700 {
701     RequireOutputStream("OutputLogTo", stream);
702     if ( ! OpenOutputLogStream(stream) ) {
703         ErrorReturnVoid( "OutputLogTo: cannot log to stream", 0L, 0L,
704                          "you can 'return;'" );
705         return False;
706     }
707     return True;
708 }
709 
710 
711 /****************************************************************************
712 **
713 *F  FuncPrint( <self>, <args> ) . . . . . . . . . . . . . . . .  print <args>
714 */
FuncPrint(Obj self,Obj args)715 static Obj FuncPrint(Obj self, Obj args)
716 {
717     volatile Obj        arg;
718     volatile UInt       i;
719     syJmp_buf           readJmpError;
720 
721     /* print all the arguments, take care of strings and functions         */
722     for ( i = 1;  i <= LEN_PLIST(args);  i++ ) {
723         arg = ELM_LIST(args,i);
724         if ( IS_PLIST(arg) && 0 < LEN_PLIST(arg) && IsStringConv(arg) ) {
725             PrintString1(arg);
726         }
727         else if ( IS_STRING_REP(arg) ) {
728             PrintString1(arg);
729         }
730         else if ( TNUM_OBJ( arg ) == T_FUNCTION ) {
731             PrintFunction( arg );
732         }
733         else {
734             memcpy( readJmpError, STATE(ReadJmpError), sizeof(syJmp_buf) );
735 
736             /* if an error occurs stop printing                            */
737             TRY_IF_NO_ERROR {
738                 PrintObj( arg );
739             }
740             CATCH_ERROR {
741                 memcpy( STATE(ReadJmpError), readJmpError, sizeof(syJmp_buf) );
742                 ReadEvalError();
743             }
744             memcpy( STATE(ReadJmpError), readJmpError, sizeof(syJmp_buf) );
745         }
746     }
747 
748     return 0;
749 }
750 
PRINT_OR_APPEND_TO_FILE_OR_STREAM(Obj args,int append,int file)751 static Obj PRINT_OR_APPEND_TO_FILE_OR_STREAM(Obj args, int append, int file)
752 {
753     const char * volatile funcname = append ? "AppendTo" : "PrintTo";
754     volatile Obj        arg;
755     volatile Obj        destination;
756     volatile UInt       i;
757     syJmp_buf           readJmpError;
758 
759     /* first entry is the file or stream                                   */
760     destination = ELM_LIST(args, 1);
761 
762     /* try to open the output and handle failures                          */
763     if (file) {
764         RequireStringRep(funcname, destination);
765         i = append ? OpenAppend(CONST_CSTR_STRING(destination))
766                    : OpenOutput(CONST_CSTR_STRING(destination));
767         if (!i) {
768             if (strcmp(CSTR_STRING(destination), "*errout*") == 0) {
769                 Panic("Failed to open *errout*!");
770             }
771             ErrorQuit("%s: cannot open '%g' for output", (Int)funcname,
772                       (Int)destination);
773         }
774     }
775     else {
776         if (CALL_1ARGS(IsOutputStream, destination) != True) {
777             ErrorQuit("%s: <outstream> must be an output stream",
778                       (Int)funcname, 0L);
779         }
780         i = OpenOutputStream(destination);
781         if (!i) {
782             ErrorQuit("%s: cannot open stream for output", (Int)funcname, 0L);
783         }
784     }
785 
786     /* print all the arguments, take care of strings and functions         */
787     for ( i = 2;  i <= LEN_PLIST(args);  i++ ) {
788         arg = ELM_LIST(args,i);
789 
790         /* if an error occurs stop printing                                */
791         memcpy(readJmpError, STATE(ReadJmpError), sizeof(syJmp_buf));
792         TRY_IF_NO_ERROR
793         {
794             if (IS_PLIST(arg) && 0 < LEN_PLIST(arg) && IsStringConv(arg)) {
795                 PrintString1(arg);
796             }
797             else if (IS_STRING_REP(arg)) {
798                 PrintString1(arg);
799             }
800             else if (IS_FUNC(arg)) {
801                 PrintFunction(arg);
802             }
803             else {
804                 PrintObj(arg);
805             }
806         }
807         CATCH_ERROR
808         {
809             CloseOutput();
810             memcpy( STATE(ReadJmpError), readJmpError, sizeof(syJmp_buf) );
811             ReadEvalError();
812         }
813         memcpy(STATE(ReadJmpError), readJmpError, sizeof(syJmp_buf));
814     }
815 
816     /* close the output file again, and return nothing                     */
817     if ( ! CloseOutput() ) {
818         ErrorQuit( "%s: cannot close output", (Int)funcname, 0L );
819     }
820 
821     return 0;
822 }
PRINT_OR_APPEND_TO(Obj args,int append)823 static Obj PRINT_OR_APPEND_TO(Obj args, int append)
824 {
825     return PRINT_OR_APPEND_TO_FILE_OR_STREAM(args, append, 1);
826 }
827 
828 
PRINT_OR_APPEND_TO_STREAM(Obj args,int append)829 static Obj PRINT_OR_APPEND_TO_STREAM(Obj args, int append)
830 {
831     return PRINT_OR_APPEND_TO_FILE_OR_STREAM(args, append, 0);
832 }
833 
834 /****************************************************************************
835 **
836 *F  FuncPRINT_TO( <self>, <args> )  . . . . . . . . . . . . . .  print <args>
837 */
FuncPRINT_TO(Obj self,Obj args)838 static Obj FuncPRINT_TO(Obj self, Obj args)
839 {
840     return PRINT_OR_APPEND_TO(args, 0);
841 }
842 
843 
844 /****************************************************************************
845 **
846 *F  FuncPRINT_TO_STREAM( <self>, <args> ) . . . . . . . . . . .  print <args>
847 */
FuncPRINT_TO_STREAM(Obj self,Obj args)848 static Obj FuncPRINT_TO_STREAM(Obj self, Obj args)
849 {
850     /* Note that FuncPRINT_TO_STREAM and FuncAPPEND_TO_STREAM do exactly the
851        same, they only differ in the function name they print as part
852        of their error messages. */
853     return PRINT_OR_APPEND_TO_STREAM(args, 0);
854 }
855 
856 
857 /****************************************************************************
858 **
859 *F  FuncAPPEND_TO( <self>, <args> ) . . . . . . . . . . . . . . append <args>
860 */
FuncAPPEND_TO(Obj self,Obj args)861 static Obj FuncAPPEND_TO(Obj self, Obj args)
862 {
863     return PRINT_OR_APPEND_TO(args, 1);
864 }
865 
866 
867 /****************************************************************************
868 **
869 *F  FuncAPPEND_TO_STREAM( <self>, <args> )  . . . . . . . . . . append <args>
870 */
FuncAPPEND_TO_STREAM(Obj self,Obj args)871 static Obj FuncAPPEND_TO_STREAM(Obj self, Obj args)
872 {
873     /* Note that FuncPRINT_TO_STREAM and FuncAPPEND_TO_STREAM do exactly the
874        same, they only differ in the function name they print as part
875        of their error messages. */
876     return PRINT_OR_APPEND_TO_STREAM(args, 1);
877 }
878 
879 
880 /****************************************************************************
881 **
882 *F  FuncREAD( <self>, <filename> )  . . . . . . . . . . . . . . . read a file
883 **
884 **  Read the current input and close the input stream.
885 */
FuncREAD(Obj self,Obj filename)886 static Obj FuncREAD(Obj self, Obj filename)
887 {
888    /* check the argument                                                  */
889     RequireStringRep("READ", filename);
890 
891     /* try to open the file                                                */
892     if ( ! OpenInput( CONST_CSTR_STRING(filename) ) ) {
893         return False;
894     }
895 
896     /* read the test file                                                  */
897     return READ() ? True : False;
898 }
899 
900 /****************************************************************************
901 **
902 *F  FuncREAD_NORECOVERY( <self>, <filename> )  . . .  . . . . . . read a file
903 **
904 **  Read the current input and close the input stream. Disable the normal
905 **  mechanism which ensures that quitting from a break loop gets you back to
906 **  a live prompt. This is initially designed for the files read from the
907 **  command line.
908 */
FuncREAD_NORECOVERY(Obj self,Obj input)909 static Obj FuncREAD_NORECOVERY(Obj self, Obj input)
910 {
911     if ( IsStringConv( input ) ) {
912         if ( ! OpenInput( CONST_CSTR_STRING(input) ) ) {
913             return False;
914         }
915     }
916     else if (CALL_1ARGS(IsInputStream, input) == True) {
917         if (!OpenInputStream(input, 0)) {
918             return False;
919         }
920     }
921     else {
922         return Fail;
923     }
924 
925     /* read the file */
926     switch (READ_NORECOVERY()) {
927     case 0: return False;
928     case 1: return True;
929     case 2: return Fail;
930     default: return Fail;
931     }
932 }
933 
934 
935 /****************************************************************************
936 **
937 *F  FuncREAD_STREAM( <self>, <stream> )   . . . . . . . . . . . read a stream
938 */
FuncREAD_STREAM(Obj self,Obj stream)939 static Obj FuncREAD_STREAM(Obj self, Obj stream)
940 {
941     RequireInputStream("READ_STREAM", stream);
942 
943     /* try to open the file                                                */
944     if (!OpenInputStream(stream, 0)) {
945         return False;
946     }
947 
948     /* read the test file                                                  */
949     return READ() ? True : False;
950 }
951 
952 /****************************************************************************
953 **
954 *F  FuncREAD_STREAM_LOOP( <self>, <instream>, <outstream> ) . . read a stream
955 **
956 **  Read data from <instream> in a read-eval-view loop and write all output
957 **  to <outstream>.
958 */
FuncREAD_STREAM_LOOP_WITH_CONTEXT(Obj self,Obj instream,Obj outstream,Obj context)959 static Obj FuncREAD_STREAM_LOOP_WITH_CONTEXT(Obj self,
960                                              Obj instream,
961                                              Obj outstream,
962                                              Obj context)
963 {
964     Int res;
965 
966     RequireInputStream("READ_STREAM_LOOP", instream);
967     RequireOutputStream("READ_STREAM_LOOP", outstream);
968 
969     if (!OpenInputStream(instream, 0)) {
970         return False;
971     }
972 
973     if (!OpenOutputStream(outstream)) {
974         res = CloseInput();
975         GAP_ASSERT(res);
976         return False;
977     }
978 
979     LockCurrentOutput(1);
980     READ_TEST_OR_LOOP(context);
981     LockCurrentOutput(0);
982 
983     res = CloseInput();
984     GAP_ASSERT(res);
985 
986     res &= CloseOutput();
987     GAP_ASSERT(res);
988 
989     return res ? True : False;
990 }
991 
FuncREAD_STREAM_LOOP(Obj self,Obj stream,Obj catcherrstdout)992 static Obj FuncREAD_STREAM_LOOP(Obj self, Obj stream, Obj catcherrstdout)
993 {
994     return FuncREAD_STREAM_LOOP_WITH_CONTEXT(self, stream, catcherrstdout,
995                                              STATE(BottomLVars));
996 }
997 /****************************************************************************
998 **
999 *F  FuncREAD_AS_FUNC( <self>, <filename> )  . . . . . . . . . . . read a file
1000 */
FuncREAD_AS_FUNC(Obj self,Obj filename)1001 static Obj FuncREAD_AS_FUNC(Obj self, Obj filename)
1002 {
1003     // check the argument
1004     RequireStringRep("READ_AS_FUNC", filename);
1005 
1006     /* try to open the file                                                */
1007     if ( ! OpenInput( CONST_CSTR_STRING(filename) ) ) {
1008         return Fail;
1009     }
1010 
1011     /* read the function                                                   */
1012     return READ_AS_FUNC();
1013 }
1014 
1015 
1016 /****************************************************************************
1017 **
1018 *F  FuncREAD_AS_FUNC_STREAM( <self>, <stream> ) . . . . . . . . read a file
1019 */
FuncREAD_AS_FUNC_STREAM(Obj self,Obj stream)1020 static Obj FuncREAD_AS_FUNC_STREAM(Obj self, Obj stream)
1021 {
1022     RequireInputStream("READ_AS_FUNC_STREAM", stream);
1023 
1024     /* try to open the file                                                */
1025     if (!OpenInputStream(stream, 0)) {
1026         return Fail;
1027     }
1028 
1029     /* read the function                                                   */
1030     return READ_AS_FUNC();
1031 }
1032 
1033 
1034 /****************************************************************************
1035 **
1036 *F  FuncREAD_GAP_ROOT( <self>, <filename> ) . . . . . . . . . . . read a file
1037 */
FuncREAD_GAP_ROOT(Obj self,Obj filename)1038 static Obj FuncREAD_GAP_ROOT(Obj self, Obj filename)
1039 {
1040     Char filenamecpy[GAP_PATH_MAX];
1041 
1042     // check the argument
1043     RequireStringRep("READ", filename);
1044 
1045     /* Copy to avoid garbage collection moving string                      */
1046     strlcpy(filenamecpy, CONST_CSTR_STRING(filename), GAP_PATH_MAX);
1047     /* try to open the file                                                */
1048     return READ_GAP_ROOT(filenamecpy) ? True : False;
1049 }
1050 
1051 
1052 /****************************************************************************
1053 **
1054 *F  FuncTmpName( <self> ) . . . . . . . . . . . . . . return a temporary name
1055 */
FuncTmpName(Obj self)1056 static Obj FuncTmpName(Obj self)
1057 {
1058     Char *              tmp;
1059     Obj                 name;
1060 
1061     tmp = SyTmpname();
1062     if ( tmp == 0 )
1063         return Fail;
1064     name = MakeString(tmp);
1065     return name;
1066 }
1067 
1068 
1069 /****************************************************************************
1070 **
1071 *F  FuncTmpDirectory( <self> )  . . . . . . . .  return a temporary directory
1072 */
FuncTmpDirectory(Obj self)1073 static Obj FuncTmpDirectory(Obj self)
1074 {
1075     Char *              tmp;
1076     Obj                 name;
1077 
1078     tmp = SyTmpdir("tm");
1079     if ( tmp == 0 )
1080         return Fail;
1081     name = MakeString(tmp);
1082     return name;
1083 }
1084 
1085 
1086 /****************************************************************************
1087 **
1088 *F  FuncRemoveFile( <self>, <name> )  . . . . . . . . . .  remove file <name>
1089 */
FuncRemoveFile(Obj self,Obj filename)1090 static Obj FuncRemoveFile(Obj self, Obj filename)
1091 {
1092     // check the argument
1093     RequireStringRep("RemoveFile", filename);
1094 
1095     /* call the system dependent function                                  */
1096     return SyRemoveFile( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
1097 }
1098 
1099 /****************************************************************************
1100 **
1101 *F  FuncCreateDir( <self>, <name> )  . . . . . . . . . . . . create directory
1102 */
FuncCreateDir(Obj self,Obj filename)1103 static Obj FuncCreateDir(Obj self, Obj filename)
1104 {
1105     // check the argument
1106     RequireStringRep("CreateDir", filename);
1107 
1108     /* call the system dependent function                                  */
1109     return SyMkdir( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
1110 }
1111 
1112 /****************************************************************************
1113 **
1114 *F  FuncRemoveDir( <self>, <name> )  . . . . . . . . . . . . remove directory
1115 */
FuncRemoveDir(Obj self,Obj filename)1116 static Obj FuncRemoveDir(Obj self, Obj filename)
1117 {
1118     // check the argument
1119     RequireStringRep("RemoveDir", filename);
1120 
1121     /* call the system dependent function                                  */
1122     return SyRmdir( CONST_CSTR_STRING(filename) ) == -1 ? Fail : True;
1123 }
1124 
1125 /****************************************************************************
1126 **
1127 *F  FuncIsDir( <self>, <name> )  . . . . . check whether something is a dir
1128 */
FuncIsDir(Obj self,Obj filename)1129 static Obj FuncIsDir(Obj self, Obj filename)
1130 {
1131     RequireStringRep("IsDir", filename);
1132 
1133     /* call the system dependent function                                  */
1134     return SyIsDir( CONST_CSTR_STRING(filename) );
1135 }
1136 
1137 
1138 
1139 
1140 /****************************************************************************
1141 **
1142 *F * * * * * * * * * * * file access test functions * * * * * * * * * * * * *
1143 */
1144 
1145 
1146 /****************************************************************************
1147 **
1148 *F  FuncLastSystemError( <self> ) .  . . . . . .  return the last system error
1149 */
1150 static UInt ErrorMessageRNam;
1151 static UInt ErrorNumberRNam;
1152 
FuncLastSystemError(Obj self)1153 static Obj FuncLastSystemError(Obj self)
1154 {
1155     Obj             err;
1156     Obj             msg;
1157 
1158     /* constructed an error record                                         */
1159     err = NEW_PREC(0);
1160 
1161     /* check if an errors has occured                                      */
1162     if ( SyLastErrorNo != 0 ) {
1163         ASS_REC( err, ErrorNumberRNam, INTOBJ_INT(SyLastErrorNo) );
1164         msg = MakeString(SyLastErrorMessage);
1165         ASS_REC( err, ErrorMessageRNam, msg );
1166     }
1167 
1168     /* no error has occured                                                */
1169     else {
1170         ASS_REC( err, ErrorNumberRNam, INTOBJ_INT(0) );
1171         msg = MakeString("no error");
1172         ASS_REC( err, ErrorMessageRNam, msg );
1173     }
1174 
1175     /* return the error record                                             */
1176     return err;
1177 }
1178 
1179 
1180 /****************************************************************************
1181 **
1182 *F  FuncIsExistingFile( <self>, <name> )  . . . . . . does file <name> exists
1183 */
FuncIsExistingFile(Obj self,Obj filename)1184 static Obj FuncIsExistingFile(Obj self, Obj filename)
1185 {
1186     Int             res;
1187 
1188     // check the argument
1189     RequireStringRep("IsExistingFile", filename);
1190 
1191     /* call the system dependent function                                  */
1192     res = SyIsExistingFile( CONST_CSTR_STRING(filename) );
1193     return res == -1 ? False : True;
1194 }
1195 
1196 
1197 /****************************************************************************
1198 **
1199 *F  FuncIsReadableFile( <self>, <name> )  . . . . . . is file <name> readable
1200 */
FuncIsReadableFile(Obj self,Obj filename)1201 static Obj FuncIsReadableFile(Obj self, Obj filename)
1202 {
1203     Int             res;
1204 
1205     // check the argument
1206     RequireStringRep("IsReadableFile", filename);
1207 
1208     /* call the system dependent function                                  */
1209     res = SyIsReadableFile( CONST_CSTR_STRING(filename) );
1210     return res == -1 ? False : True;
1211 }
1212 
1213 
1214 /****************************************************************************
1215 **
1216 *F  FuncIsWritableFile( <self>, <name> )  . . . . . . is file <name> writable
1217 */
FuncIsWritableFile(Obj self,Obj filename)1218 static Obj FuncIsWritableFile(Obj self, Obj filename)
1219 {
1220     Int             res;
1221 
1222     // check the argument
1223     RequireStringRep("IsWritableFile", filename);
1224 
1225     /* call the system dependent function                                  */
1226     res = SyIsWritableFile( CONST_CSTR_STRING(filename) );
1227     return res == -1 ? False : True;
1228 }
1229 
1230 
1231 /****************************************************************************
1232 **
1233 *F  FuncIsExecutableFile( <self>, <name> )  . . . . is file <name> executable
1234 */
FuncIsExecutableFile(Obj self,Obj filename)1235 static Obj FuncIsExecutableFile(Obj self, Obj filename)
1236 {
1237     Int             res;
1238 
1239     // check the argument
1240     RequireStringRep("IsExecutableFile", filename);
1241 
1242     /* call the system dependent function                                  */
1243     res = SyIsExecutableFile( CONST_CSTR_STRING(filename) );
1244     return res == -1 ? False : True;
1245 }
1246 
1247 
1248 /****************************************************************************
1249 **
1250 *F  FuncIsDirectoryPath( <self>, <name> ) . . . .  is file <name> a directory
1251 */
FuncIsDirectoryPathString(Obj self,Obj filename)1252 static Obj FuncIsDirectoryPathString(Obj self, Obj filename)
1253 {
1254     Int             res;
1255 
1256     // check the argument
1257     RequireStringRep("IsDirectoryPathString", filename);
1258 
1259     /* call the system dependent function                                  */
1260     res = SyIsDirectoryPath( CONST_CSTR_STRING(filename) );
1261     return res == -1 ? False : True;
1262 }
1263 
1264 
1265 /****************************************************************************
1266 **
1267 *F  FuncLIST_DIR( <self>, <dirname> ) . . . read names of files in dir
1268 **
1269 **  This function returns a GAP list which contains the names of all files
1270 **  contained in a directory <dirname>.
1271 **
1272 **  If <dirname> could not be opened as a directory 'fail' is returned. The
1273 **  reason for the error can be found with 'LastSystemError();' in GAP.
1274 **
1275 */
FuncLIST_DIR(Obj self,Obj dirname)1276 static Obj FuncLIST_DIR(Obj self, Obj dirname)
1277 {
1278     DIR *dir;
1279     struct dirent *entry;
1280     Obj res;
1281 
1282     // check the argument
1283     RequireStringRep("LIST_DIR", dirname);
1284 
1285     SyClearErrorNo();
1286     dir = opendir(CONST_CSTR_STRING(dirname));
1287     if (dir == NULL) {
1288         SySetErrorNo();
1289         return Fail;
1290     }
1291     res = NEW_PLIST(T_PLIST, 16);
1292     while ((entry = readdir(dir))) {
1293         PushPlist(res, MakeImmString(entry->d_name));
1294     }
1295     closedir(dir);
1296     return res;
1297 }
1298 
1299 /****************************************************************************
1300 **
1301 *F * * * * * * * * * * * * text stream functions  * * * * * * * * * * * * * *
1302 */
1303 
1304 /****************************************************************************
1305 **
1306 *F  FuncCLOSE_FILE( <self>, <fid> ) . . . . . . . . . . . . .  close a stream
1307 */
FuncCLOSE_FILE(Obj self,Obj fid)1308 static Obj FuncCLOSE_FILE(Obj self, Obj fid)
1309 {
1310     // check the argument
1311     Int ifid = GetSmallInt("CLOSE_FILE", fid);
1312 
1313     /* call the system dependent function                                  */
1314     Int ret = SyFclose( ifid );
1315     return ret == -1 ? Fail : True;
1316 }
1317 
1318 
1319 /****************************************************************************
1320 **
1321 *F  FuncINPUT_TEXT_FILE( <self>, <name> ) . . . . . . . . . . . open a stream
1322 */
FuncINPUT_TEXT_FILE(Obj self,Obj filename)1323 static Obj FuncINPUT_TEXT_FILE(Obj self, Obj filename)
1324 {
1325     Int             fid;
1326 
1327     // check the argument
1328     RequireStringRep("INPUT_TEXT_FILE", filename);
1329 
1330     /* call the system dependent function                                  */
1331     SyClearErrorNo();
1332     fid = SyFopen( CONST_CSTR_STRING(filename), "r" );
1333     if ( fid == - 1)
1334         SySetErrorNo();
1335     return fid == -1 ? Fail : INTOBJ_INT(fid);
1336 }
1337 
1338 
1339 /****************************************************************************
1340 **
1341 *F  FuncIS_END_OF_FILE( <self>, <fid> ) . . . . . . . . . . .  is end of file
1342 */
FuncIS_END_OF_FILE(Obj self,Obj fid)1343 static Obj FuncIS_END_OF_FILE(Obj self, Obj fid)
1344 {
1345     // check the argument
1346     Int ifid = GetSmallInt("IS_END_OF_FILE", fid);
1347 
1348     Int ret = SyIsEndOfFile( ifid );
1349     return ret == -1 ? Fail : ( ret == 0 ? False : True );
1350 }
1351 
1352 
1353 /****************************************************************************
1354 **
1355 *F  FuncOUTPUT_TEXT_FILE( <self>, <name>, <append> )  . . . . . open a stream
1356 */
FuncOUTPUT_TEXT_FILE(Obj self,Obj filename,Obj append)1357 static Obj FuncOUTPUT_TEXT_FILE(Obj self, Obj filename, Obj append)
1358 {
1359     Int             fid;
1360 
1361     // check the argument
1362     RequireStringRep("OUTPUT_TEXT_FILE", filename);
1363     RequireTrueOrFalse("OUTPUT_TEXT_FILE", append);
1364 
1365     /* call the system dependent function                                  */
1366     SyClearErrorNo();
1367     if ( append == True ) {
1368         fid = SyFopen( CONST_CSTR_STRING(filename), "a" );
1369     }
1370     else {
1371         fid = SyFopen( CONST_CSTR_STRING(filename), "w" );
1372     }
1373     if ( fid == - 1)
1374         SySetErrorNo();
1375     return fid == -1 ? Fail : INTOBJ_INT(fid);
1376 }
1377 
1378 
1379 /****************************************************************************
1380 **
1381 *F  FuncPOSITION_FILE( <self>, <fid> )  . . . . . . . . .  position of stream
1382 */
FuncPOSITION_FILE(Obj self,Obj fid)1383 static Obj FuncPOSITION_FILE(Obj self, Obj fid)
1384 {
1385     // check the argument
1386     Int ifid = GetSmallInt("POSITION_FILE", fid);
1387 
1388     Int ret = SyFtell(ifid);
1389 
1390     // Return if failed
1391     if (ret == -1) {
1392         return Fail;
1393     }
1394 
1395     return INTOBJ_INT(ret);
1396 }
1397 
1398 
1399 
1400 /****************************************************************************
1401 **
1402 *F  FuncREAD_BYTE_FILE( <self>, <fid> ) . . . . . . . . . . . . . read a byte
1403 */
FuncREAD_BYTE_FILE(Obj self,Obj fid)1404 static Obj FuncREAD_BYTE_FILE(Obj self, Obj fid)
1405 {
1406     // check the argument
1407     Int ifid = GetSmallInt("READ_BYTE_FILE", fid);
1408 
1409     /* call the system dependent function                                  */
1410     Int ret = SyGetch( ifid );
1411 
1412     return ret == EOF ? Fail : INTOBJ_INT(ret);
1413 }
1414 
1415 
1416 /****************************************************************************
1417 **
1418 *F  FuncREAD_LINE_FILE( <self>, <fid> ) . . . . . . . . . . . . . read a line
1419 **
1420 **  This uses fgets and works only if there are no zero characters in <fid>.
1421 */
FuncREAD_LINE_FILE(Obj self,Obj fid)1422 static Obj FuncREAD_LINE_FILE(Obj self, Obj fid)
1423 {
1424     Char            buf[256];
1425     Char *          cstr;
1426     Int             len, buflen;
1427     UInt            lstr;
1428     Obj             str;
1429 
1430     // check the argument
1431     Int ifid = GetSmallInt("READ_LINE_FILE", fid);
1432 
1433     /* read <fid> until we see a newline or eof or we've read at least
1434        one byte and more are not immediately available */
1435     str = NEW_STRING(0);
1436     len = 0;
1437     while (1) {
1438       if ( len > 0 && !HasAvailableBytes(ifid))
1439         break;
1440       len += 255;
1441       GROW_STRING( str, len );
1442       if ( SyFgetsSemiBlock( buf, 256, ifid ) == 0 )
1443         break;
1444       buflen = strlen(buf);
1445       lstr = GET_LEN_STRING(str);
1446       cstr = CSTR_STRING(str) + lstr;
1447       memcpy( cstr, buf, buflen+1 );
1448       SET_LEN_STRING(str, lstr+buflen);
1449       if ( buf[buflen-1] == '\n' )
1450         break;
1451     }
1452 
1453     /* fix the length of <str>                                             */
1454     len = GET_LEN_STRING(str);
1455     ResizeBag( str, SIZEBAG_STRINGLEN(len) );
1456 
1457     /* and return                                                          */
1458     return len == 0 ? Fail : str;
1459 }
1460 
1461 /****************************************************************************
1462 **
1463 *F  FuncREAD_ALL_FILE( <self>, <fid>, <limit> )  . . . . . . . read remainder
1464 **
1465 ** more precisely, read until either
1466 **   (a) we have read at least one byte and no more are available
1467 **   (b) we have evidence that it will never be possible to read a byte
1468 **   (c) we have read <limit> bytes (-1 indicates no limit)
1469 */
1470 
FuncREAD_ALL_FILE(Obj self,Obj fid,Obj limit)1471 static Obj FuncREAD_ALL_FILE(Obj self, Obj fid, Obj limit)
1472 {
1473     Char            buf[20000];
1474     Int             len;
1475     // Length of string read this loop (or negative for error)
1476     UInt            lstr;
1477     Obj             str;
1478     UInt            csize;
1479 
1480     // check the argument
1481     Int ifid = GetSmallInt("READ_ALL_FILE", fid);
1482 
1483     Int ilim = GetSmallInt("READ_ALL_FILE", limit);
1484 
1485     /* read <fid> until we see  eof or we've read at least
1486        one byte and more are not immediately available */
1487     str = NEW_STRING(0);
1488     len = 0;
1489     lstr = 0;
1490 
1491 #ifdef SYS_IS_CYGWIN32
1492  getmore:
1493 #endif
1494     while (ilim == -1 || len < ilim ) {
1495       lstr = 0;
1496       if ( len > 0 && !HasAvailableBytes(ifid))
1497           break;
1498       if (SyBufIsTTY(ifid)) {
1499           if (ilim == -1) {
1500               Pr("#W Warning -- reading to  end of input tty will never "
1501                  "end\n",
1502                  0, 0);
1503               csize = 20000;
1504           }
1505           else
1506               csize = ((ilim - len) > 20000) ? 20000 : ilim - len;
1507 
1508           if (SyFgetsSemiBlock(buf, csize, ifid))
1509               lstr = strlen(buf);
1510           else
1511               lstr = 0;
1512       }
1513       else {
1514           do {
1515               csize =
1516                   (ilim == -1 || (ilim - len) > 20000) ? 20000 : ilim - len;
1517               lstr = SyReadWithBuffer(ifid, buf, csize);
1518           } while (lstr == -1 && errno == EAGAIN);
1519       }
1520       if (lstr <= 0) {
1521           SyBufSetEOF(ifid);
1522           break;
1523       }
1524       GROW_STRING( str, len+lstr );
1525       memcpy(CHARS_STRING(str)+len, buf, lstr);
1526       len += lstr;
1527       SET_LEN_STRING(str, len);
1528     }
1529 
1530     /* fix the length of <str>                                             */
1531     len = GET_LEN_STRING(str);
1532 #ifdef SYS_IS_CYGWIN32
1533     /* line end hackery */
1534     UInt i = 0, j = 0;
1535     while (i < len) {
1536         if (CHARS_STRING(str)[i] == '\r') {
1537             if (i < len - 1 && CHARS_STRING(str)[i + 1] == '\n') {
1538                 i++;
1539                 continue;
1540             }
1541             else
1542                 CHARS_STRING(str)[i] = '\n';
1543         }
1544         CHARS_STRING(str)[j++] = CHARS_STRING(str)[i++];
1545     }
1546     len = j;
1547     SET_LEN_STRING(str, len);
1548     // If we have not yet read enough, and have read at least one character this loop, then read more
1549     if (ilim != -1 && len < ilim && lstr > 0)
1550         goto getmore;
1551 #endif
1552     ResizeBag( str, SIZEBAG_STRINGLEN(len) );
1553 
1554     /* and return                                                          */
1555     return len == 0 ? Fail : str;
1556 }
1557 
1558 /****************************************************************************
1559 **
1560 *F  FuncSEEK_POSITION_FILE( <self>, <fid>, <pos> )  . seek position of stream
1561 */
FuncSEEK_POSITION_FILE(Obj self,Obj fid,Obj pos)1562 static Obj FuncSEEK_POSITION_FILE(Obj self, Obj fid, Obj pos)
1563 {
1564     Int             ret;
1565 
1566     // check the argument
1567     Int ifid = GetSmallInt("SEEK_POSITION_FILE", fid);
1568     Int ipos = GetSmallInt("SEEK_POSITION_FILE", pos);
1569 
1570     ret = SyFseek( ifid, ipos );
1571     return ret == -1 ? Fail : True;
1572 }
1573 
1574 
1575 /****************************************************************************
1576 **
1577 *F  FuncWRITE_BYTE_FILE( <self>, <fid>, <byte> )  . . . . . . .  write a byte
1578 */
FuncWRITE_BYTE_FILE(Obj self,Obj fid,Obj ch)1579 static Obj FuncWRITE_BYTE_FILE(Obj self, Obj fid, Obj ch)
1580 {
1581     // check the argument
1582     Int ifid = GetSmallInt("WRITE_BYTE_FILE", fid);
1583     Int ich = GetSmallInt("WRITE_BYTE_FILE", ch);
1584 
1585     /* call the system dependent function                                  */
1586     Int ret = SyEchoch( ich, ifid );
1587     return ret == -1 ? Fail : True;
1588 }
1589 
1590 /****************************************************************************
1591 **
1592 *F  FuncWRITE_STRING_FILE_NC( <self>, <fid>, <string> ) .write a whole string
1593 */
FuncWRITE_STRING_FILE_NC(Obj self,Obj fid,Obj str)1594 static Obj FuncWRITE_STRING_FILE_NC(Obj self, Obj fid, Obj str)
1595 {
1596     Int             len = 0, l, ret;
1597     const char      *ptr;
1598 
1599     /* don't check the argument                                            */
1600 
1601     len = GET_LEN_STRING(str);
1602     ptr = CONST_CSTR_STRING(str);
1603     while (len > 0) {
1604       l = (len > 1048576) ? 1048576 : len;
1605       ret = SyWrite(INT_INTOBJ(fid), ptr, l);
1606       if (ret == -1) {
1607         SySetErrorNo();
1608         return Fail;
1609       }
1610       len -= ret;
1611       ptr += ret;
1612     }
1613     return True;
1614 }
1615 
FuncREAD_STRING_FILE(Obj self,Obj fid)1616 static Obj FuncREAD_STRING_FILE(Obj self, Obj fid)
1617 {
1618     // check the argument
1619     Int ifid = GetSmallInt("READ_STRING_FILE", fid);
1620     return SyReadStringFid(ifid);
1621 }
1622 
1623 /****************************************************************************
1624 **
1625 *F  FuncFD_OF_FILE( <fid> )
1626 */
FuncFD_OF_FILE(Obj self,Obj fid)1627 static Obj FuncFD_OF_FILE(Obj self, Obj fid)
1628 {
1629     Int fd = GetSmallInt("FD_OF_FILE", fid);
1630     Int fdi = SyBufFileno(fd);
1631     return INTOBJ_INT(fdi);
1632 }
1633 
1634 #ifdef HPCGAP
FuncRAW_MODE_FILE(Obj self,Obj fid,Obj onoff)1635 static Obj FuncRAW_MODE_FILE(Obj self, Obj fid, Obj onoff)
1636 {
1637     Int fd = GetSmallInt("RAW_MODE_FILE", fid);
1638     if (onoff == False || onoff == Fail) {
1639         syStopraw(fd);
1640         return False;
1641     }
1642     else
1643         return syStartraw(fd) ? True : False;
1644 }
1645 #endif
1646 
1647 #ifdef HAVE_SELECT
FuncUNIXSelect(Obj self,Obj inlist,Obj outlist,Obj exclist,Obj timeoutsec,Obj timeoutusec)1648 static Obj FuncUNIXSelect(Obj self,
1649                           Obj inlist,
1650                           Obj outlist,
1651                           Obj exclist,
1652                           Obj timeoutsec,
1653                           Obj timeoutusec)
1654 {
1655   fd_set infds,outfds,excfds;
1656   struct timeval tv;
1657   int n,maxfd;
1658   Int i,j;
1659   Obj o;
1660 
1661   RequirePlainList("UNIXSelect", inlist);
1662   RequirePlainList("UNIXSelect", outlist);
1663   RequirePlainList("UNIXSelect", exclist);
1664 
1665   FD_ZERO(&infds);
1666   FD_ZERO(&outfds);
1667   FD_ZERO(&excfds);
1668   maxfd = 0;
1669   /* Handle input file descriptors: */
1670   for (i = 1;i <= LEN_PLIST(inlist);i++) {
1671     o = ELM_PLIST(inlist,i);
1672     if (o != (Obj) 0 && IS_INTOBJ(o)) {
1673       j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1674       FD_SET(j,&infds);
1675       if (j > maxfd) maxfd = j;
1676     }
1677   }
1678   /* Handle output file descriptors: */
1679   for (i = 1;i <= LEN_PLIST(outlist);i++) {
1680     o = ELM_PLIST(outlist,i);
1681     if (o != (Obj) 0 && IS_INTOBJ(o)) {
1682       j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1683       FD_SET(j,&outfds);
1684       if (j > maxfd) maxfd = j;
1685     }
1686   }
1687   /* Handle exception file descriptors: */
1688   for (i = 1;i <= LEN_PLIST(exclist);i++) {
1689     o = ELM_PLIST(exclist,i);
1690     if (o != (Obj) 0 && IS_INTOBJ(o)) {
1691       j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1692       FD_SET(j,&excfds);
1693       if (j > maxfd) maxfd = j;
1694     }
1695   }
1696   /* Handle the timeout: */
1697   if (timeoutsec != (Obj) 0 && IS_INTOBJ(timeoutsec) &&
1698       timeoutusec != (Obj) 0 && IS_INTOBJ(timeoutusec)) {
1699     tv.tv_sec = INT_INTOBJ(timeoutsec);
1700     tv.tv_usec = INT_INTOBJ(timeoutusec);
1701     n = select(maxfd+1,&infds,&outfds,&excfds,&tv);
1702   } else {
1703     n = select(maxfd+1,&infds,&outfds,&excfds,NULL);
1704   }
1705 
1706   if (n >= 0) {
1707     /* Now run through the lists and call functions if ready: */
1708 
1709     for (i = 1;i <= LEN_PLIST(inlist);i++) {
1710       o = ELM_PLIST(inlist,i);
1711       if (o != (Obj) 0 && IS_INTOBJ(o)) {
1712         j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1713         if (!(FD_ISSET(j,&infds))) {
1714           SET_ELM_PLIST(inlist,i,Fail);
1715           CHANGED_BAG(inlist);
1716         }
1717       }
1718     }
1719     /* Handle output file descriptors: */
1720     for (i = 1;i <= LEN_PLIST(outlist);i++) {
1721       o = ELM_PLIST(outlist,i);
1722       if (o != (Obj) 0 && IS_INTOBJ(o)) {
1723         j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1724         if (!(FD_ISSET(j,&outfds))) {
1725           SET_ELM_PLIST(outlist,i,Fail);
1726           CHANGED_BAG(outlist);
1727         }
1728       }
1729     }
1730     /* Handle exception file descriptors: */
1731     for (i = 1;i <= LEN_PLIST(exclist);i++) {
1732       o = ELM_PLIST(exclist,i);
1733       if (o != (Obj) 0 && IS_INTOBJ(o)) {
1734         j = INT_INTOBJ(o);  /* a UNIX file descriptor */
1735         if (!(FD_ISSET(j,&excfds))) {
1736           SET_ELM_PLIST(exclist,i,Fail);
1737           CHANGED_BAG(exclist);
1738         }
1739       }
1740     }
1741   }
1742   return INTOBJ_INT(n);
1743 }
1744 #endif
1745 
1746 /****************************************************************************
1747 **
1748 *F * * * * * * * * * * * * * execution functions  * * * * * * * * * * * * * *
1749 */
1750 
1751 
1752 /****************************************************************************
1753 **
1754 *F  FuncExecuteProcess( <self>, <dir>, <prg>, <in>, <out>, <args> )   process
1755 */
1756 static Obj
FuncExecuteProcess(Obj self,Obj dir,Obj prg,Obj in,Obj out,Obj args)1757 FuncExecuteProcess(Obj self, Obj dir, Obj prg, Obj in, Obj out, Obj args)
1758 {
1759     Obj    ExecArgs[1024];
1760     Char * ExecCArgs[1024];
1761 
1762     Obj                 tmp;
1763     Int                 res;
1764     Int                 i;
1765 
1766     // check the argument
1767     RequireStringRep("ExecuteProcess", dir);
1768     RequireStringRep("ExecuteProcess", prg);
1769     Int iin = GetSmallInt("ExecuteProcess", in);
1770     Int iout = GetSmallInt("ExecuteProcess", out);
1771     RequireSmallList("ExecuteProcess", args);
1772 
1773     /* create an argument array                                            */
1774     for ( i = 1;  i <= LEN_LIST(args);  i++ ) {
1775         if ( i == 1023 )
1776             break;
1777         tmp = ELM_LIST( args, i );
1778         RequireStringRep("ExecuteProcess", tmp);
1779         ExecArgs[i] = tmp;
1780     }
1781     ExecCArgs[0]   = CSTR_STRING(prg);
1782     ExecCArgs[i] = 0;
1783     for ( i--;  0 < i;  i-- ) {
1784         ExecCArgs[i] = CSTR_STRING(ExecArgs[i]);
1785     }
1786     if (SyWindow && out == INTOBJ_INT(1)) /* standard output */
1787       syWinPut( INT_INTOBJ(out), "@z","");
1788 
1789     /* execute the process                                                 */
1790     res = SyExecuteProcess( CSTR_STRING(dir),
1791                             CSTR_STRING(prg),
1792                             iin,
1793                             iout,
1794                             ExecCArgs );
1795 
1796     if (SyWindow && out == INTOBJ_INT(1)) /* standard output */
1797       syWinPut( INT_INTOBJ(out), "@mAgIc","");
1798     return res == 255 ? Fail : INTOBJ_INT(res);
1799 }
1800 
1801 
1802 /****************************************************************************
1803 **
1804 *F * * * * * * * * * * * * * initialize module * * * * * * * * * * * * * * *
1805 */
1806 
1807 /****************************************************************************
1808 **
1809 *V  GVarFuncs . . . . . . . . . . . . . . . . . . list of functions to export
1810 */
1811 static StructGVarFunc GVarFuncs[] = {
1812 
1813     GVAR_FUNC(READ, 1, "filename"),
1814     GVAR_FUNC(READ_NORECOVERY, 1, "filename"),
1815     GVAR_FUNC(READ_ALL_COMMANDS, 4, "instream, echo, capture, outputFunc"),
1816     GVAR_FUNC(READ_COMMAND_REAL, 2, "stream, echo"),
1817     GVAR_FUNC(READ_STREAM, 1, "stream"),
1818     GVAR_FUNC(READ_STREAM_LOOP, 2, "stream, catchstderrout"),
1819     GVAR_FUNC(READ_STREAM_LOOP_WITH_CONTEXT, 3, "stream, catchstderrout, context"),
1820     GVAR_FUNC(READ_AS_FUNC, 1, "filename"),
1821     GVAR_FUNC(READ_AS_FUNC_STREAM, 1, "stream"),
1822     GVAR_FUNC(READ_GAP_ROOT, 1, "filename"),
1823     GVAR_FUNC(LOG_TO, 1, "filename"),
1824     GVAR_FUNC(LOG_TO_STREAM, 1, "filename"),
1825     GVAR_FUNC(CLOSE_LOG_TO, 0, ""),
1826     GVAR_FUNC(INPUT_LOG_TO, 1, "filename"),
1827     GVAR_FUNC(INPUT_LOG_TO_STREAM, 1, "filename"),
1828     GVAR_FUNC(CLOSE_INPUT_LOG_TO, 0, ""),
1829     GVAR_FUNC(OUTPUT_LOG_TO, 1, "filename"),
1830     GVAR_FUNC(OUTPUT_LOG_TO_STREAM, 1, "filename"),
1831     GVAR_FUNC(CLOSE_OUTPUT_LOG_TO, 0, ""),
1832     GVAR_FUNC(Print, -1, "args"),
1833     GVAR_FUNC(PRINT_TO, -1, "args"),
1834     GVAR_FUNC(PRINT_TO_STREAM, -1, "args"),
1835     GVAR_FUNC(APPEND_TO, -1, "args"),
1836     GVAR_FUNC(APPEND_TO_STREAM, -1, "args"),
1837     GVAR_FUNC(TmpName, 0, ""),
1838     GVAR_FUNC(TmpDirectory, 0, ""),
1839     GVAR_FUNC(RemoveFile, 1, "filename"),
1840     GVAR_FUNC(CreateDir, 1, "filename"),
1841     GVAR_FUNC(RemoveDir, 1, "filename"),
1842     GVAR_FUNC(IsDir, 1, "filename"),
1843     GVAR_FUNC(LastSystemError, 0, ""),
1844     GVAR_FUNC(IsExistingFile, 1, "filename"),
1845     GVAR_FUNC(IsReadableFile, 1, "filename"),
1846     GVAR_FUNC(IsWritableFile, 1, "filename"),
1847     GVAR_FUNC(IsExecutableFile, 1, "filename"),
1848     GVAR_FUNC(IsDirectoryPathString, 1, "filename"),
1849     GVAR_FUNC(LIST_DIR, 1, "dirname"),
1850     GVAR_FUNC(CLOSE_FILE, 1, "fid"),
1851     GVAR_FUNC(INPUT_TEXT_FILE, 1, "filename"),
1852     GVAR_FUNC(OUTPUT_TEXT_FILE, 2, "filename, append"),
1853     GVAR_FUNC(IS_END_OF_FILE, 1, "fid"),
1854     GVAR_FUNC(POSITION_FILE, 1, "fid"),
1855     GVAR_FUNC(READ_BYTE_FILE, 1, "fid"),
1856     GVAR_FUNC(READ_LINE_FILE, 1, "fid"),
1857     GVAR_FUNC(READ_ALL_FILE, 2, "fid, limit"),
1858     GVAR_FUNC(SEEK_POSITION_FILE, 2, "fid, pos"),
1859     GVAR_FUNC(WRITE_BYTE_FILE, 2, "fid, byte"),
1860     GVAR_FUNC(WRITE_STRING_FILE_NC, 2, "fid, string"),
1861     GVAR_FUNC(READ_STRING_FILE, 1, "fid"),
1862     GVAR_FUNC(FD_OF_FILE, 1, "fid"),
1863 #ifdef HPCGAP
1864     GVAR_FUNC(RAW_MODE_FILE, 2, "fid, bool"),
1865 #endif
1866 #ifdef HAVE_SELECT
1867     GVAR_FUNC(
1868         UNIXSelect, 5, "inlist, outlist, exclist, timeoutsec, timeoutusec"),
1869 #endif
1870     GVAR_FUNC(ExecuteProcess, 5, "dir, prg, in, out, args"),
1871     { 0, 0, 0, 0, 0 }
1872 
1873 };
1874 
1875 
1876 /****************************************************************************
1877 **
1878 *F  InitKernel( <module> )  . . . . . . . . initialise kernel data structures
1879 */
InitKernel(StructInitInfo * module)1880 static Int InitKernel (
1881     StructInitInfo *    module )
1882 {
1883     /* init filters and functions                                          */
1884     InitHdlrFuncsFromTable( GVarFuncs );
1885 
1886     ImportFuncFromLibrary( "IsInputStream", &IsInputStream );
1887     ImportFuncFromLibrary( "IsOutputStream", &IsOutputStream );
1888 
1889     /* return success                                                      */
1890     return 0;
1891 }
1892 
1893 
1894 /****************************************************************************
1895 **
1896 *F  PostRestore( <module> ) . . . . . . . . . . . . . after restore workspace
1897 */
PostRestore(StructInitInfo * module)1898 static Int PostRestore (
1899     StructInitInfo *    module )
1900 {
1901     /* file access test functions                                          */
1902     ErrorNumberRNam  = RNamName("number");
1903     ErrorMessageRNam = RNamName("message");
1904 
1905     /* pick up the number of this global */
1906     LastReadValueGVar = GVarName("LastReadValue");
1907 
1908     /* return success                                                      */
1909     return 0;
1910 }
1911 
1912 
1913 /****************************************************************************
1914 **
1915 *F  InitLibrary( <module> ) . . . . . . .  initialise library data structures
1916 */
InitLibrary(StructInitInfo * module)1917 static Int InitLibrary (
1918     StructInitInfo *    module )
1919 {
1920     /* init filters and functions                                          */
1921     InitGVarFuncsFromTable( GVarFuncs );
1922 
1923 
1924     /* return success                                                      */
1925     return PostRestore( module );
1926 }
1927 
1928 
1929 /****************************************************************************
1930 **
1931 *F  InitInfoStreams() . . . . . . . . . . . . . . . . table of init functions
1932 */
1933 static StructInitInfo module = {
1934     // init struct using C99 designated initializers; for a full list of
1935     // fields, please refer to the definition of StructInitInfo
1936     .type = MODULE_BUILTIN,
1937     .name = "streams",
1938     .initKernel = InitKernel,
1939     .initLibrary = InitLibrary,
1940     .postRestore = PostRestore,
1941 };
1942 
InitInfoStreams(void)1943 StructInitInfo * InitInfoStreams ( void )
1944 {
1945     return &module;
1946 }
1947