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