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 **
11 **  This file will contains the functions for communicating with other
12 **  processes via ptys and sockets
13 **
14 **  The eventual intent is that there will be InputOutputStreams at the GAP
15 **  level with some API to be defined, and two ways of creating them. One is
16 **  like Process except that the external process is left running, the other
17 **  connects as client to a specified socket.
18 **
19 **  At this level, we provide the two interfaces separately. For each we have
20 **  an integer identifer for each open connection, and creation, read and
21 **  write functions, and possibly some sort of probe function.
22 **
23 */
24 
25 #include "iostream.h"
26 
27 #include "bool.h"
28 #include "error.h"
29 #include "integer.h"
30 #include "io.h"
31 #include "lists.h"
32 #include "modules.h"
33 #include "stringobj.h"
34 #include "sysenv.h"
35 
36 #include "hpc/thread.h"
37 
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <termios.h>
42 #include <unistd.h>
43 
44 #ifdef HAVE_SPAWN_H
45 #include <spawn.h>
46 #endif
47 
48 #ifdef HAVE_SYS_WAIT_H
49 #include <sys/wait.h>
50 #endif
51 
52 #ifdef HAVE_OPENPTY
53   #if defined(HAVE_UTIL_H)
54     #include <util.h>     /* for openpty() on Mac OS X, OpenBSD and NetBSD */
55   #elif defined(HAVE_LIBUTIL_H)
56     #include <libutil.h>  /* for openpty() on FreeBSD */
57   #elif defined(HAVE_PTY_H)
58     #include <pty.h>      /* for openpty() on Cygwin, Interix, OSF/1 4 and 5 */
59   #endif
60 #endif
61 
62 
63 // LOCKING
64 // In HPC-GAP, be sure to HashLock PtyIOStreams before accessing any of
65 // the IOStream related variables, including FreeptyIOStreams
66 
67 typedef struct {
68   pid_t childPID;   /* Also used as a link to make a linked free list */
69   int ptyFD;      /* GAP reading from external prog */
70   int inuse;      /* we need to scan all the "live" structures when we have
71                      had SIGCHLD so, for now, we just walk the array for
72                      the ones marked in use */
73   int changed;    /* set non-zero by the signal handler if our child has
74                      done something -- stopped or exited */
75   int status;     /* status from wait3 -- meaningful only if changed is 1 */
76   int blocked;    /* we have already reported a problem, which is still there */
77   int alive;      /* gets set after waiting for a child actually fails
78                      implying that the child has vanished under our noses */
79 } PtyIOStream;
80 
81 enum {
82     /* maximal number of pseudo ttys we will allocate */
83     MAX_PTYS = 64,
84 
85     /* maximal length of argument string for CREATE_PTY_IOSTREAM */
86     MAX_ARGS = 1000
87 };
88 
89 static PtyIOStream PtyIOStreams[MAX_PTYS];
90 
91 // FreePtyIOStreams is the index of the first unused slot of the PtyIOStreams
92 // array, or else -1 if there is none. The childPID field of each free slot in
93 // turn is set to the position of the next free slot (or -1), so that the free
94 // slots form a linked list.
95 static Int FreePtyIOStreams;
96 
NewStream(void)97 static Int NewStream(void)
98 {
99     Int stream = -1;
100     if (FreePtyIOStreams != -1) {
101         stream = FreePtyIOStreams;
102         FreePtyIOStreams = PtyIOStreams[stream].childPID;
103     }
104     return stream;
105 }
106 
FreeStream(UInt stream)107 static void FreeStream(UInt stream)
108 {
109     PtyIOStreams[stream].childPID = FreePtyIOStreams;
110     FreePtyIOStreams = stream;
111 }
112 
113 /****************************************************************************
114 **
115 *F  SignalChild(<stream>) . .. . . . . . . . . .  interrupt the child process
116 */
SignalChild(UInt stream,UInt sig)117 static void SignalChild(UInt stream, UInt sig)
118 {
119     if (PtyIOStreams[stream].childPID != -1) {
120         kill(PtyIOStreams[stream].childPID, sig);
121     }
122 }
123 
124 /****************************************************************************
125 **
126 *F  KillChild(<stream>) . . . . . . . . . . . . . . .  kill the child process
127 */
KillChild(UInt stream)128 static void KillChild(UInt stream)
129 {
130     if (PtyIOStreams[stream].childPID != -1) {
131         close(PtyIOStreams[stream].ptyFD);
132         SignalChild(stream, SIGKILL);
133     }
134 }
135 
136 
137 /****************************************************************************
138 **
139 */
140 #define PErr(msg)                                                            \
141     Pr(msg ": %s (errnor %d)\n", (Int)strerror(errno), (Int)errno);
142 
143 /****************************************************************************
144 **
145 *F  OpenPty( <master>, <slave> ) . . . . . . . . open a pty master/slave pair
146 */
147 
148 #ifdef HAVE_OPENPTY
149 
OpenPty(int * master,int * slave)150 static UInt OpenPty(int * master, int * slave)
151 {
152     /* openpty is available on OpenBSD, NetBSD and FreeBSD, Mac OS X,
153        Cygwin, Interix, OSF/1 4 and 5, and glibc (since 1998), and hence
154        on most modern Linux systems. See also:
155        https://www.gnu.org/software/gnulib/manual/html_node/openpty.html */
156     return (openpty(master, slave, NULL, NULL, NULL) < 0);
157 }
158 
159 #elif defined(HAVE_POSIX_OPENPT)
160 
OpenPty(int * master,int * slave)161 static UInt OpenPty(int * master, int * slave)
162 {
163     /* Attempt to use POSIX 98 pseudo ttys. Opening a master tty is done
164        via posix_openpt, which is available on virtually every current
165        UNIX system; indeed, according to gnulib, it is available on at
166        least the following systems:
167          - glibc >= 2.2.1 (released January 2001; but is a stub on GNU/Hurd),
168          - Mac OS X >= 10.4 (released April 2005),
169          - FreeBSD >= 5.1 (released June 2003),
170          - NetBSD >= 3.0 (released December 2005),
171          - AIX >= 5.2 (released October 2002),
172          - HP-UX >= 11.31 (released February 2007),
173          - Solaris >= 10 (released January 2005),
174          - Cygwin >= 1.7 (released December 2009).
175        Systems lacking posix_openpt (in addition to older versions of
176        the systems listed above) include:
177          - OpenBSD
178          - Minix 3.1.8
179          - IRIX 6.5
180          - OSF/1 5.1
181          - mingw
182          - MSVC 9
183          - Interix 3.5
184          - BeOS
185        */
186     *master = posix_openpt(O_RDWR | O_NOCTTY);
187     if (*master < 0) {
188         PErr("OpenPty: posix_openpt failed");
189         return 1;
190     }
191 
192     if (grantpt(*master)) {
193         PErr("OpenPty: grantpt failed");
194         goto error;
195     }
196     if (unlockpt(*master)) {
197         close(*master);
198         PErr("OpenPty: unlockpt failed");
199         goto error;
200     }
201 
202     *slave = open(ptsname(*master), O_RDWR, 0);
203     if (*slave < 0) {
204         PErr("OpenPty: opening slave tty failed");
205         goto error;
206     }
207     return 0;
208 
209 error:
210     close(*master);
211     return 1;
212 }
213 
214 #else
215 
OpenPty(int * master,int * slave)216 static UInt OpenPty(int * master, int * slave)
217 {
218     Pr("no pseudo tty support available\n", 0L, 0L);
219     return 1;
220 }
221 
222 #endif
223 
224 
225 /****************************************************************************
226 **
227 *F  StartChildProcess( <dir>, <name>, <args> )
228 **  Start a subprocess using ptys. Returns the stream number of the IOStream
229 **  that is connected to the new processs
230 */
231 
232 
233 // Clean up a signalled or exited child process
234 // CheckChildStatusChanged must be called by libraries which replace GAP's
235 // signal handler, or call 'waitpid'.
236 // The function should be passed a PID, and the return value of waitpid.
237 // Returns 1 if that PID was a child owned by GAP, or 0 otherwise.
CheckChildStatusChanged(int childPID,int status)238 int CheckChildStatusChanged(int childPID, int status)
239 {
240     GAP_ASSERT(childPID > 0);
241     GAP_ASSERT((WIFEXITED(status) || WIFSIGNALED(status)));
242     HashLock(PtyIOStreams);
243     for (UInt i = 0; i < MAX_PTYS; i++) {
244         if (PtyIOStreams[i].inuse && PtyIOStreams[i].childPID == childPID) {
245             PtyIOStreams[i].changed = 1;
246             PtyIOStreams[i].status = status;
247             PtyIOStreams[i].blocked = 0;
248             HashUnlock(PtyIOStreams);
249             return 1;
250         }
251     }
252     HashUnlock(PtyIOStreams);
253     return 0;
254 }
255 
ChildStatusChanged(int whichsig)256 static void ChildStatusChanged(int whichsig)
257 {
258     UInt i;
259     int  status;
260     int  retcode;
261     assert(whichsig == SIGCHLD);
262     HashLock(PtyIOStreams);
263     for (i = 0; i < MAX_PTYS; i++) {
264         if (PtyIOStreams[i].inuse) {
265             retcode = waitpid(PtyIOStreams[i].childPID, &status,
266                               WNOHANG | WUNTRACED);
267             if (retcode != -1 && retcode != 0 &&
268                 (WIFEXITED(status) || WIFSIGNALED(status))) {
269                 PtyIOStreams[i].changed = 1;
270                 PtyIOStreams[i].status = status;
271                 PtyIOStreams[i].blocked = 0;
272             }
273         }
274     }
275     HashUnlock(PtyIOStreams);
276 
277 #if !defined(HPCGAP)
278     /* Collect up any other zombie children */
279     do {
280         retcode = waitpid(-1, &status, WNOHANG);
281         if (retcode == -1 && errno != ECHILD)
282             Pr("#E Unexpected waitpid error %d\n", errno, 0);
283     } while (retcode != 0 && retcode != -1);
284 
285     signal(SIGCHLD, ChildStatusChanged);
286 #endif
287 }
288 
289 #ifdef HPCGAP
FuncDEFAULT_SIGCHLD_HANDLER(Obj self)290 static Obj FuncDEFAULT_SIGCHLD_HANDLER(Obj self)
291 {
292     ChildStatusChanged(SIGCHLD);
293     return (Obj)0;
294 }
295 
296 
297 // HACK: since we can't use posix_spawn in a thread-safe manner, disable
298 // it for HPC-GAP
299 #undef HAVE_POSIX_SPAWN
300 
301 #endif
302 
303 static Int
StartChildProcess(const Char * dir,const Char * prg,Char * args[])304 StartChildProcess(const Char * dir, const Char * prg, Char * args[])
305 {
306     int slave; /* pipe to child                   */
307     Int stream;
308 #ifdef HAVE_POSIX_SPAWN
309     int oldwd = -1;
310 #endif
311 
312     struct termios tst; /* old and new terminal state      */
313 
314     HashLock(PtyIOStreams);
315 
316     /* Get a stream record */
317     stream = NewStream();
318     if (stream == -1) {
319         HashUnlock(PtyIOStreams);
320         return -1;
321     }
322 
323     /* open pseudo terminal for communication with gap */
324     if (OpenPty(&PtyIOStreams[stream].ptyFD, &slave)) {
325         PErr("StartChildProcess: open pseudo tty failed");
326         FreeStream(stream);
327         HashUnlock(PtyIOStreams);
328         return -1;
329     }
330 
331     /* Now fiddle with the terminal sessions on the pty */
332     if (tcgetattr(slave, &tst) == -1) {
333         PErr("StartChildProcess: tcgetattr on slave pty failed");
334         goto cleanup;
335     }
336     tst.c_cc[VINTR] = 0377;
337     tst.c_cc[VQUIT] = 0377;
338     tst.c_iflag    &= ~(INLCR|ICRNL);
339     tst.c_cc[VMIN]  = 1;
340     tst.c_cc[VTIME] = 0;
341     tst.c_lflag    &= ~(ECHO|ICANON);
342     tst.c_oflag    &= ~(ONLCR);
343     if (tcsetattr(slave, TCSANOW, &tst) == -1) {
344         PErr("StartChildProcess: tcsetattr on slave pty failed");
345         goto cleanup;
346     }
347 
348     /* set input to non blocking operation */
349     /* Not any more */
350 
351     PtyIOStreams[stream].inuse = 1;
352     PtyIOStreams[stream].alive = 1;
353     PtyIOStreams[stream].blocked = 0;
354     PtyIOStreams[stream].changed = 0;
355     /* fork */
356 #ifdef HAVE_POSIX_SPAWN
357     posix_spawn_file_actions_t file_actions;
358 
359     // setup file actions
360     if (posix_spawn_file_actions_init(&file_actions)) {
361         PErr("StartChildProcess: posix_spawn_file_actions_init failed");
362         goto cleanup;
363     }
364 
365     if (posix_spawn_file_actions_addclose(&file_actions,
366                                           PtyIOStreams[stream].ptyFD)) {
367         PErr("StartChildProcess: posix_spawn_file_actions_addclose failed");
368         posix_spawn_file_actions_destroy(&file_actions);
369         goto cleanup;
370     }
371 
372     if (posix_spawn_file_actions_adddup2(&file_actions, slave, 0)) {
373         PErr("StartChildProcess: "
374              "posix_spawn_file_actions_adddup2(slave, 0) failed");
375         posix_spawn_file_actions_destroy(&file_actions);
376         goto cleanup;
377     }
378 
379     if (posix_spawn_file_actions_adddup2(&file_actions, slave, 1)) {
380         PErr("StartChildProcess: "
381              "posix_spawn_file_actions_adddup2(slave, 1) failed");
382         posix_spawn_file_actions_destroy(&file_actions);
383         goto cleanup;
384     }
385 
386     // temporarily change the working directory
387     //
388     // WARNING: This is not thread safe! Unfortunately, there is no portable
389     // way to do this race free, without using an external shim executable
390     // which sets the wd and then calls the actually target executable. But at
391     // least this well-known deficiency has finally been realized as a problem
392     // by POSIX in 2018, just about 14 years after posix_spawn was first put
393     // into the standard), and so we might see a proper fix for this soon,
394     // i.e., possibly even within the next decade!
395     // See also <http://austingroupbugs.net/view.php?id=1208>
396     oldwd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
397     if (oldwd == -1) {
398         PErr("StartChildProcess: cannot open current working "
399              "directory");
400         posix_spawn_file_actions_destroy(&file_actions);
401         goto cleanup;
402     }
403     if (chdir(dir) == -1) {
404         PErr("StartChildProcess: cannot change working "
405              "directory for subprocess");
406         posix_spawn_file_actions_destroy(&file_actions);
407         goto cleanup;
408     }
409 
410     // spawn subprocess
411     if (posix_spawn(&PtyIOStreams[stream].childPID, prg, &file_actions, 0,
412                     args, environ)) {
413         PErr("StartChildProcess: posix_spawn failed");
414         goto cleanup;
415     }
416 
417     // restore working directory
418     if (fchdir(oldwd)) {
419         PErr("StartChildProcess: failed to restore working dir after "
420              "spawning");
421     }
422     close(oldwd);    // ignore error
423     oldwd = -1;
424 
425     // cleanup
426     if (posix_spawn_file_actions_destroy(&file_actions)) {
427         PErr("StartChildProcess: posix_spawn_file_actions_destroy failed");
428         goto cleanup;
429     }
430 #else
431     PtyIOStreams[stream].childPID = fork();
432     if (PtyIOStreams[stream].childPID == 0) {
433         /* Set up the child */
434         close(PtyIOStreams[stream].ptyFD);
435         if (dup2(slave, 0) == -1)
436             _exit(-1);
437         fcntl(0, F_SETFD, 0);
438 
439         if (dup2(slave, 1) == -1)
440             _exit(-1);
441         fcntl(1, F_SETFD, 0);
442 
443         if (chdir(dir) == -1) {
444             _exit(-1);
445         }
446 
447 #ifdef HAVE_SETPGID
448         setpgid(0, 0);
449 #endif
450 
451         execv(prg, args);
452 
453         /* This should never happen */
454         close(slave);
455         _exit(1);
456     }
457 #endif
458 
459     /* Now we're back in the master */
460     /* check if the fork was successful */
461     if (PtyIOStreams[stream].childPID == -1) {
462         PErr("StartChildProcess: cannot fork to subprocess");
463         goto cleanup;
464     }
465     close(slave);
466 
467 
468     HashUnlock(PtyIOStreams);
469     return stream;
470 
471 cleanup:
472 #ifdef HAVE_POSIX_SPAWN
473     if (oldwd >= 0) {
474         // restore working directory
475         if (fchdir(oldwd)) {
476             PErr("StartChildProcess: failed to restore working dir during "
477                  "cleanup");
478         }
479         close(oldwd);
480     }
481 #endif
482     close(slave);
483     close(PtyIOStreams[stream].ptyFD);
484     PtyIOStreams[stream].inuse = 0;
485     FreeStream(stream);
486     HashUnlock(PtyIOStreams);
487     return -1;
488 }
489 
490 
491 // This function assumes that the caller invoked HashLock(PtyIOStreams).
492 // It unlocks just before throwing any error.
HandleChildStatusChanges(UInt pty)493 static void HandleChildStatusChanges(UInt pty)
494 {
495     /* common error handling, when we are asked to read or write to a stopped
496        or dead child */
497     if (PtyIOStreams[pty].alive == 0) {
498         PtyIOStreams[pty].changed = 0;
499         PtyIOStreams[pty].blocked = 0;
500         HashUnlock(PtyIOStreams);
501         ErrorQuit("Child Process is unexpectedly dead", (Int)0L, (Int)0L);
502     }
503     else if (PtyIOStreams[pty].blocked) {
504         HashUnlock(PtyIOStreams);
505         ErrorQuit("Child Process is still dead", (Int)0L, (Int)0L);
506     }
507     else if (PtyIOStreams[pty].changed) {
508         PtyIOStreams[pty].blocked = 1;
509         PtyIOStreams[pty].changed = 0;
510         Int cPID = PtyIOStreams[pty].childPID;
511         Int status = PtyIOStreams[pty].status;
512         HashUnlock(PtyIOStreams);
513         ErrorQuit("Child Process %d has stopped or died, status %d", cPID,
514                   status);
515     }
516 }
517 
FuncCREATE_PTY_IOSTREAM(Obj self,Obj dir,Obj prog,Obj args)518 static Obj FuncCREATE_PTY_IOSTREAM(Obj self, Obj dir, Obj prog, Obj args)
519 {
520     Obj    allargs[MAX_ARGS + 1];
521     Char * argv[MAX_ARGS + 2];
522     UInt   i, len;
523     Int    pty;
524     len = LEN_LIST(args);
525     if (len > MAX_ARGS)
526         ErrorQuit("Too many arguments", 0, 0);
527     ConvString(dir);
528     ConvString(prog);
529     for (i = 1; i <= len; i++) {
530         allargs[i] = ELM_LIST(args, i);
531         ConvString(allargs[i]);
532     }
533     /* From here we cannot afford to have a garbage collection */
534     argv[0] = CSTR_STRING(prog);
535     for (i = 1; i <= len; i++) {
536         argv[i] = CSTR_STRING(allargs[i]);
537     }
538     argv[i] = (Char *)0;
539     pty = StartChildProcess(CONST_CSTR_STRING(dir), CONST_CSTR_STRING(prog),
540                             argv);
541     if (pty < 0)
542         return Fail;
543     else
544         return ObjInt_Int(pty);
545 }
546 
547 
ReadFromPty2(UInt stream,Char * buf,Int maxlen,UInt block)548 static Int ReadFromPty2(UInt stream, Char * buf, Int maxlen, UInt block)
549 {
550     /* read at most maxlen bytes from stream, into buf.
551       If block is non-zero then wait for at least one byte
552       to be available. Otherwise don't. Return the number of
553       bytes read, or -1 for error. A blocking return having read zero bytes
554       definitely indicates an end of file */
555 
556     Int nread = 0;
557     int ret;
558 
559     while (maxlen > 0) {
560 #ifdef HAVE_SELECT
561         if (!block || nread > 0) {
562             fd_set         set;
563             struct timeval tv;
564             do {
565                 FD_ZERO(&set);
566                 FD_SET(PtyIOStreams[stream].ptyFD, &set);
567                 tv.tv_sec = 0;
568                 tv.tv_usec = 0;
569                 ret = select(PtyIOStreams[stream].ptyFD + 1, &set, NULL, NULL,
570                              &tv);
571             } while (ret == -1 && errno == EAGAIN);
572             if (ret == -1 && nread == 0)
573                 return -1;
574             if (ret < 1)
575                 return nread ? nread : -1;
576         }
577 #endif
578         do {
579             ret = read(PtyIOStreams[stream].ptyFD, buf, maxlen);
580         } while (ret == -1 && errno == EAGAIN);
581         if (ret == -1 && nread == 0)
582             return -1;
583         if (ret < 1)
584             return nread;
585         nread += ret;
586         buf += ret;
587         maxlen -= ret;
588     }
589     return nread;
590 }
591 
592 
WriteToPty(UInt stream,Char * buf,Int len)593 static UInt WriteToPty(UInt stream, Char * buf, Int len)
594 {
595     Int res;
596     Int old;
597     if (len < 0) {
598         // FIXME: why allow 'len' to be negative here? To allow
599         // invoking a "raw" version of `write` perhaps? But we don't
600         // seem to use that anywhere. So perhaps get rid of it or
601         // even turn it into an error?!
602         return write(PtyIOStreams[stream].ptyFD, buf, -len);
603     }
604     old = len;
605     while (0 < len) {
606         res = write(PtyIOStreams[stream].ptyFD, buf, len);
607         if (res < 0) {
608             HandleChildStatusChanges(stream);
609             if (errno == EAGAIN) {
610                 continue;
611             }
612             else
613                 // FIXME: by returning errno, we make it impossible for the
614                 // caller to detect errors.
615                 return errno;
616         }
617         len -= res;
618         buf += res;
619     }
620     return old;
621 }
622 
HashLockStreamIfAvailable(Obj stream)623 static UInt HashLockStreamIfAvailable(Obj stream)
624 {
625     UInt pty = INT_INTOBJ(stream);
626     HashLock(PtyIOStreams);
627     if (!PtyIOStreams[pty].inuse) {
628         HashUnlock(PtyIOStreams);
629         ErrorMayQuit("IOSTREAM %d is not in use", pty, 0L);
630     }
631     return pty;
632 }
633 
FuncWRITE_IOSTREAM(Obj self,Obj stream,Obj string,Obj len)634 static Obj FuncWRITE_IOSTREAM(Obj self, Obj stream, Obj string, Obj len)
635 {
636     UInt pty = HashLockStreamIfAvailable(stream);
637 
638     HandleChildStatusChanges(pty);
639     ConvString(string);
640     UInt result = WriteToPty(pty, CSTR_STRING(string), INT_INTOBJ(len));
641     HashUnlock(PtyIOStreams);
642     return ObjInt_Int(result);
643 }
644 
FuncREAD_IOSTREAM(Obj self,Obj stream,Obj len)645 static Obj FuncREAD_IOSTREAM(Obj self, Obj stream, Obj len)
646 {
647     UInt pty = HashLockStreamIfAvailable(stream);
648 
649     /* HandleChildStatusChanges(pty);   Omit this to allow picking up
650      * "trailing" bytes*/
651     Obj string = NEW_STRING(INT_INTOBJ(len));
652     Int ret = ReadFromPty2(pty, CSTR_STRING(string), INT_INTOBJ(len), 1);
653     HashUnlock(PtyIOStreams);
654     if (ret == -1)
655         return Fail;
656     SET_LEN_STRING(string, ret);
657     ResizeBag(string, SIZEBAG_STRINGLEN(ret));
658     return string;
659 }
660 
FuncREAD_IOSTREAM_NOWAIT(Obj self,Obj stream,Obj len)661 static Obj FuncREAD_IOSTREAM_NOWAIT(Obj self, Obj stream, Obj len)
662 {
663     UInt pty = HashLockStreamIfAvailable(stream);
664 
665     /* HandleChildStatusChanges(pty);   Omit this to allow picking up
666      * "trailing" bytes*/
667     Obj string = NEW_STRING(INT_INTOBJ(len));
668     Int ret = ReadFromPty2(pty, CSTR_STRING(string), INT_INTOBJ(len), 0);
669     HashUnlock(PtyIOStreams);
670     if (ret == -1)
671         return Fail;
672     SET_LEN_STRING(string, ret);
673     ResizeBag(string, SIZEBAG_STRINGLEN(ret));
674     return string;
675 }
676 
FuncKILL_CHILD_IOSTREAM(Obj self,Obj stream)677 static Obj FuncKILL_CHILD_IOSTREAM(Obj self, Obj stream)
678 {
679     UInt pty = HashLockStreamIfAvailable(stream);
680 
681     /* Don't check for child having changes status */
682     KillChild(pty);
683 
684     HashUnlock(PtyIOStreams);
685     return 0;
686 }
687 
FuncSIGNAL_CHILD_IOSTREAM(Obj self,Obj stream,Obj sig)688 static Obj FuncSIGNAL_CHILD_IOSTREAM(Obj self, Obj stream, Obj sig)
689 {
690     UInt pty = HashLockStreamIfAvailable(stream);
691 
692     /* Don't check for child having changes status */
693     SignalChild(pty, INT_INTOBJ(sig));
694 
695     HashUnlock(PtyIOStreams);
696     return 0;
697 }
698 
FuncCLOSE_PTY_IOSTREAM(Obj self,Obj stream)699 static Obj FuncCLOSE_PTY_IOSTREAM(Obj self, Obj stream)
700 {
701     UInt pty = HashLockStreamIfAvailable(stream);
702 
703     /* Close down the child */
704     int status;
705     int retcode = close(PtyIOStreams[pty].ptyFD);
706     if (retcode)
707         Pr("Strange close return code %d\n", retcode, 0);
708     kill(PtyIOStreams[pty].childPID, SIGTERM);
709     // GAP (or another library) might wait on this PID before
710     // we handle it. If that happens, waitpid will return -1.
711     retcode = waitpid(PtyIOStreams[pty].childPID, &status, WNOHANG);
712     if (retcode == 0) {
713         // Give process a second to quit
714         SySleep(1);
715         retcode = waitpid(PtyIOStreams[pty].childPID, &status, WNOHANG);
716     }
717     if (retcode == 0) {
718         // Hard kill process
719         kill(PtyIOStreams[pty].childPID, SIGKILL);
720         retcode = waitpid(PtyIOStreams[pty].childPID, &status, 0);
721     }
722 
723     PtyIOStreams[pty].inuse = 0;
724 
725     FreeStream(pty);
726     HashUnlock(PtyIOStreams);
727     return 0;
728 }
729 
FuncIS_BLOCKED_IOSTREAM(Obj self,Obj stream)730 static Obj FuncIS_BLOCKED_IOSTREAM(Obj self, Obj stream)
731 {
732     UInt pty = HashLockStreamIfAvailable(stream);
733 
734     int isBlocked = (PtyIOStreams[pty].blocked || PtyIOStreams[pty].changed ||
735                      !PtyIOStreams[pty].alive);
736     HashUnlock(PtyIOStreams);
737     return isBlocked ? True : False;
738 }
739 
FuncFD_OF_IOSTREAM(Obj self,Obj stream)740 static Obj FuncFD_OF_IOSTREAM(Obj self, Obj stream)
741 {
742     UInt pty = HashLockStreamIfAvailable(stream);
743 
744     Obj result = ObjInt_Int(PtyIOStreams[pty].ptyFD);
745     HashUnlock(PtyIOStreams);
746     return result;
747 }
748 
749 
750 /****************************************************************************
751 **
752 *F * * * * * * * * * * * * * initialize module * * * * * * * * * * * * * * *
753 */
754 
755 /****************************************************************************
756 **
757 *V  GVarFuncs . . . . . . . . . . . . . . . . . . list of functions to export
758 */
759 static StructGVarFunc GVarFuncs[] = {
760 
761     GVAR_FUNC(CREATE_PTY_IOSTREAM, 3, "dir, prog, args"),
762     GVAR_FUNC(WRITE_IOSTREAM, 3, "stream, string, len"),
763     GVAR_FUNC(READ_IOSTREAM, 2, "stream, len"),
764     GVAR_FUNC(READ_IOSTREAM_NOWAIT, 2, "stream, len"),
765     GVAR_FUNC(KILL_CHILD_IOSTREAM, 1, "stream"),
766     GVAR_FUNC(CLOSE_PTY_IOSTREAM, 1, "stream"),
767     GVAR_FUNC(SIGNAL_CHILD_IOSTREAM, 2, "stream, signal"),
768     GVAR_FUNC(IS_BLOCKED_IOSTREAM, 1, "stream"),
769     GVAR_FUNC(FD_OF_IOSTREAM, 1, "stream"),
770 #ifdef HPCGAP
771     GVAR_FUNC(DEFAULT_SIGCHLD_HANDLER, 0, ""),
772 #endif
773 
774     { 0, 0, 0, 0, 0 }
775 };
776 
777 /* FIXME/TODO: should probably do some checks preSave for open files etc and
778    refuse to save if any are found */
779 
780 /****************************************************************************
781 **
782 *F  InitKernel( <module> ) . . . . . . .  initialise kernel data structures
783 */
InitKernel(StructInitInfo * module)784 static Int InitKernel(StructInitInfo * module)
785 {
786     UInt i;
787     PtyIOStreams[0].childPID = -1;
788     for (i = 1; i < MAX_PTYS; i++) {
789         PtyIOStreams[i].childPID = i - 1;
790         PtyIOStreams[i].inuse = 0;
791     }
792     FreePtyIOStreams = MAX_PTYS - 1;
793 
794     /* init filters and functions                                          */
795     InitHdlrFuncsFromTable(GVarFuncs);
796 
797 #if !defined(HPCGAP)
798     /* Set up the trap to detect future dying children */
799     signal(SIGCHLD, ChildStatusChanged);
800 #endif
801 
802     return 0;
803 }
804 
805 /****************************************************************************
806 **
807 *F  InitLibrary( <module> ) . . . . . . .  initialise library data structures
808 */
809 
InitLibrary(StructInitInfo * module)810 static Int InitLibrary(StructInitInfo * module)
811 {
812     /* init filters and functions                                          */
813     InitGVarFuncsFromTable(GVarFuncs);
814 
815     return 0;
816 }
817 
818 /****************************************************************************
819 **
820 *F  InitInfoSysFiles()  . . . . . . . . . . . . . . . table of init functions
821 */
822 static StructInitInfo module = {
823     // init struct using C99 designated initializers; for a full list of
824     // fields, please refer to the definition of StructInitInfo
825     .type = MODULE_BUILTIN,
826     .name = "iostream",
827     .initKernel = InitKernel,
828     .initLibrary = InitLibrary,
829 };
830 
InitInfoIOStream(void)831 StructInitInfo * InitInfoIOStream(void)
832 {
833     return &module;
834 }
835