1"""
2    :codeauthor: Pedro Algarvio (pedro@algarvio.me)
3
4
5    salt.utils.vt
6    ~~~~~~~~~~~~~
7
8    Virtual Terminal
9
10    This code has been heavily inspired by Python's subprocess code, the `non
11    blocking version of it`__, some minor online snippets about TTY handling
12    with python including `Python's own ``pty`` source code`__ and `Pexpect`__
13    which has already surpassed some of the pitfalls that some systems would
14    get us into.
15
16    .. __: http://code.activestate.com/recipes/440554/
17    .. __: https://github.com/python-mirror/python/blob/3.3/Lib/pty.py
18    .. __: https://github.com/pexpect/pexpect
19
20"""
21
22import errno
23import functools
24import logging
25import os
26import select
27import signal
28import subprocess
29import sys
30import time
31
32import salt.utils.crypt
33import salt.utils.data
34import salt.utils.stringutils
35from salt.log.setup import LOG_LEVELS
36
37mswindows = sys.platform == "win32"
38
39try:
40    # pylint: disable=F0401,W0611
41    from win32file import ReadFile, WriteFile
42    from win32pipe import PeekNamedPipe
43    import msvcrt
44    import win32api
45    import win32con
46    import win32process
47
48    # pylint: enable=F0401,W0611
49except ImportError:
50    import pty
51    import fcntl
52    import struct
53    import termios
54
55
56log = logging.getLogger(__name__)
57
58
59class TerminalException(Exception):
60    """
61    Terminal specific exception
62    """
63
64
65def setwinsize(child, rows=80, cols=80):
66    """
67    This sets the terminal window size of the child tty. This will
68    cause a SIGWINCH signal to be sent to the child. This does not
69    change the physical window size. It changes the size reported to
70    TTY-aware applications like vi or curses -- applications that
71    respond to the SIGWINCH signal.
72
73    Thank you for the shortcut PEXPECT
74    """
75    TIOCSWINSZ = getattr(termios, "TIOCSWINSZ", -2146929561)
76    if TIOCSWINSZ == 2148037735:
77        # Same bits, but with sign.
78        TIOCSWINSZ = -2146929561
79    # Note, assume ws_xpixel and ws_ypixel are zero.
80    packed = struct.pack(b"HHHH", rows, cols, 0, 0)
81    fcntl.ioctl(child, TIOCSWINSZ, packed)
82
83
84def getwinsize(child):
85    """
86    This returns the terminal window size of the child tty. The return
87    value is a tuple of (rows, cols).
88
89    Thank you for the shortcut PEXPECT
90    """
91    TIOCGWINSZ = getattr(termios, "TIOCGWINSZ", 1074295912)
92    packed = struct.pack(b"HHHH", 0, 0, 0, 0)
93    ioctl = fcntl.ioctl(child, TIOCGWINSZ, packed)
94    return struct.unpack(b"HHHH", ioctl)[0:2]
95
96
97class Terminal:
98    """
99    I'm a virtual terminal
100    """
101
102    def __init__(
103        self,
104        args=None,
105        executable=None,
106        shell=False,
107        cwd=None,
108        env=None,
109        preexec_fn=None,
110        # Terminal Size
111        rows=None,
112        cols=None,
113        # Logging options
114        log_stdin=None,
115        log_stdin_level="debug",
116        log_stdout=None,
117        log_stdout_level="debug",
118        log_stderr=None,
119        log_stderr_level="debug",
120        # sys.stdXYZ streaming options
121        stream_stdout=None,
122        stream_stderr=None,
123        # Used for tests
124        force_receive_encoding=__salt_system_encoding__,
125    ):
126        if not args and not executable:
127            raise TerminalException(
128                'You need to pass at least one of "args", "executable" '
129            )
130        self.args = args
131        self.executable = executable
132        self.shell = shell
133        self.cwd = cwd
134        self.env = env
135        self.preexec_fn = preexec_fn
136        self.receive_encoding = force_receive_encoding
137
138        if rows is None and cols is None:
139            rows, cols = self.__detect_parent_terminal_size()
140        elif rows is not None and cols is None:
141            _, cols = self.__detect_parent_terminal_size()
142        elif rows is None and cols is not None:
143            rows, _ = self.__detect_parent_terminal_size()
144        self.rows = rows
145        self.cols = cols
146        self.pid = None
147        self.stdin = None
148        self.stdout = None
149        self.stderr = None
150
151        self.child_fd = None
152        self.child_fde = None
153
154        self.partial_data_stdout = b""
155        self.partial_data_stderr = b""
156
157        self.closed = True
158        self.flag_eof_stdout = False
159        self.flag_eof_stderr = False
160        self.terminated = True
161        self.exitstatus = None
162        self.signalstatus = None
163        # status returned by os.waitpid
164        self.status = None
165
166        if stream_stdout is True:
167            self.stream_stdout = sys.stdout
168        elif stream_stdout is False:
169            self.stream_stdout = None
170        elif stream_stdout is not None:
171            if (
172                not hasattr(stream_stdout, "write")
173                or not hasattr(stream_stdout, "flush")
174                or not hasattr(stream_stdout, "close")
175            ):
176                raise TerminalException(
177                    "'stream_stdout' needs to have at least 3 methods, "
178                    "'write()', 'flush()' and 'close()'."
179                )
180            self.stream_stdout = stream_stdout
181        else:
182            raise TerminalException(
183                "Don't know how to handle '{}' as the VT's "
184                "'stream_stdout' parameter.".format(stream_stdout)
185            )
186
187        if stream_stderr is True:
188            self.stream_stderr = sys.stderr
189        elif stream_stderr is False:
190            self.stream_stderr = None
191        elif stream_stderr is not None:
192            if (
193                not hasattr(stream_stderr, "write")
194                or not hasattr(stream_stderr, "flush")
195                or not hasattr(stream_stderr, "close")
196            ):
197                raise TerminalException(
198                    "'stream_stderr' needs to have at least 3 methods, "
199                    "'write()', 'flush()' and 'close()'."
200                )
201            self.stream_stderr = stream_stderr
202        else:
203            raise TerminalException(
204                "Don't know how to handle '{}' as the VT's "
205                "'stream_stderr' parameter.".format(stream_stderr)
206            )
207
208        try:
209            self._spawn()
210        except Exception as err:  # pylint: disable=W0703
211            # A lot can go wrong, so that's why we're catching the most general
212            # exception type
213            log.warning(
214                "Failed to spawn the VT: %s", err, exc_info_on_loglevel=logging.DEBUG
215            )
216            raise TerminalException("Failed to spawn the VT. Error: {}".format(err))
217
218        log.debug(
219            "Child Forked! PID: %s  STDOUT_FD: %s  STDERR_FD: %s",
220            self.pid,
221            self.child_fd,
222            self.child_fde,
223        )
224        terminal_command = " ".join(self.args)
225        if (
226            'decode("base64")' in terminal_command
227            or "base64.b64decode(" in terminal_command
228        ):
229            log.debug("VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE")
230            log.trace("Terminal Command: %s", terminal_command)
231        else:
232            log.debug("Terminal Command: %s", terminal_command)
233
234        # Setup logging after spawned in order to have a pid value
235        self.stdin_logger_level = LOG_LEVELS.get(log_stdin_level, log_stdin_level)
236        if log_stdin is True:
237            self.stdin_logger = logging.getLogger(
238                "{}.{}.PID-{}.STDIN".format(__name__, self.__class__.__name__, self.pid)
239            )
240        elif log_stdin is not None:
241            if not isinstance(log_stdin, logging.Logger):
242                raise RuntimeError("'log_stdin' needs to subclass `logging.Logger`")
243            self.stdin_logger = log_stdin
244        else:
245            self.stdin_logger = None
246
247        self.stdout_logger_level = LOG_LEVELS.get(log_stdout_level, log_stdout_level)
248        if log_stdout is True:
249            self.stdout_logger = logging.getLogger(
250                "{}.{}.PID-{}.STDOUT".format(
251                    __name__, self.__class__.__name__, self.pid
252                )
253            )
254        elif log_stdout is not None:
255            if not isinstance(log_stdout, logging.Logger):
256                raise RuntimeError("'log_stdout' needs to subclass `logging.Logger`")
257            self.stdout_logger = log_stdout
258        else:
259            self.stdout_logger = None
260
261        self.stderr_logger_level = LOG_LEVELS.get(log_stderr_level, log_stderr_level)
262        if log_stderr is True:
263            self.stderr_logger = logging.getLogger(
264                "{}.{}.PID-{}.STDERR".format(
265                    __name__, self.__class__.__name__, self.pid
266                )
267            )
268        elif log_stderr is not None:
269            if not isinstance(log_stderr, logging.Logger):
270                raise RuntimeError("'log_stderr' needs to subclass `logging.Logger`")
271            self.stderr_logger = log_stderr
272        else:
273            self.stderr_logger = None
274
275    def send(self, data):
276        """
277        Send data to the terminal. You are responsible to send any required
278        line feeds.
279        """
280        return self._send(data)
281
282    def sendline(self, data, linesep=os.linesep):
283        """
284        Send the provided data to the terminal appending a line feed.
285        """
286        return self.send("{}{}".format(data, linesep))
287
288    def recv(self, maxsize=None):
289        """
290        Receive data from the terminal as a (``stdout``, ``stderr``) tuple. If
291        any of those is ``None`` we can no longer communicate with the
292        terminal's child process.
293        """
294        if maxsize is None:
295            maxsize = 1024
296        elif maxsize < 1:
297            maxsize = 1
298        return self._recv(maxsize)
299
300    def close(self, terminate=True, kill=False):
301        """
302        Close the communication with the terminal's child.
303        If ``terminate`` is ``True`` then additionally try to terminate the
304        terminal, and if ``kill`` is also ``True``, kill the terminal if
305        terminating it was not enough.
306        """
307        if not self.closed:
308            if self.child_fd is not None:
309                os.close(self.child_fd)
310                self.child_fd = None
311            if self.child_fde is not None:
312                os.close(self.child_fde)
313                self.child_fde = None
314            time.sleep(0.1)
315            if terminate:
316                if not self.terminate(kill):
317                    raise TerminalException("Failed to terminate child process.")
318            self.closed = True
319
320    @property
321    def has_unread_data(self):
322        return self.flag_eof_stderr is False or self.flag_eof_stdout is False
323
324    def _translate_newlines(self, data):
325        if data is None or not data:
326            return
327        # PTY's always return \r\n as the line feeds
328        return data.replace("\r\n", os.linesep)
329
330    def __enter__(self):
331        return self
332
333    def __exit__(self, exc_type, exc_value, traceback):
334        self.close(terminate=True, kill=True)
335        # Wait for the process to terminate, to avoid zombies.
336        if self.isalive():
337            self.wait()
338
339    if mswindows:
340
341        def _execute(self):
342            raise NotImplementedError
343
344        def _spawn(self):
345            raise NotImplementedError
346
347        def _recv(self, maxsize):
348            raise NotImplementedError
349
350        def _send(self, data):
351            raise NotImplementedError
352
353        def send_signal(self, sig):
354            """
355            Send a signal to the process
356            """
357            # pylint: disable=E1101
358            if sig == signal.SIGTERM:
359                self.terminate()
360            elif sig == signal.CTRL_C_EVENT:
361                os.kill(self.pid, signal.CTRL_C_EVENT)
362            elif sig == signal.CTRL_BREAK_EVENT:
363                os.kill(self.pid, signal.CTRL_BREAK_EVENT)
364            else:
365                raise ValueError("Unsupported signal: {}".format(sig))
366            # pylint: enable=E1101
367
368        def terminate(self, force=False):
369            """
370            Terminates the process
371            """
372            try:
373                win32api.TerminateProcess(self._handle, 1)
374            except OSError:
375                # ERROR_ACCESS_DENIED (winerror 5) is received when the
376                # process already died.
377                ecode = win32process.GetExitCodeProcess(self._handle)
378                if ecode == win32con.STILL_ACTIVE:
379                    raise
380                self.exitstatus = ecode
381
382        kill = terminate
383    else:
384
385        def _spawn(self):
386            if not isinstance(self.args, str) and self.shell is True:
387                self.args = " ".join(self.args)
388            parent, child = pty.openpty()
389            err_parent, err_child = os.pipe()
390            child_name = os.ttyname(child)
391            proc = subprocess.Popen(  # pylint: disable=subprocess-popen-preexec-fn
392                self.args,
393                preexec_fn=functools.partial(
394                    self._preexec, child_name, self.rows, self.cols, self.preexec_fn
395                ),
396                shell=self.shell,  # nosec
397                executable=self.executable,
398                cwd=self.cwd,
399                stdin=child,
400                stdout=child,
401                stderr=err_child,
402                env=self.env,
403                close_fds=True,
404            )
405            os.close(child)
406            os.close(err_child)
407            self.child_fd = parent
408            self.child_fde = err_parent
409            self.pid = proc.pid
410            self.closed = False
411            self.terminated = False
412
413        @staticmethod
414        def _preexec(child_name, rows=80, cols=80, preexec_fn=None):
415            # Disconnect from controlling tty. Harmless if not already
416            # connected
417            try:
418                tty_fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
419                if tty_fd >= 0:
420                    os.close(tty_fd)
421            # which exception, shouldn't we catch explicitly .. ?
422            except Exception:  # pylint: disable=broad-except
423                # Already disconnected. This happens if running inside cron
424                pass
425            try:
426                os.setsid()
427            except OSError:
428                pass
429            try:
430                tty_fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
431                if tty_fd >= 0:
432                    os.close(tty_fd)
433                    raise TerminalException(
434                        "Could not open child pty, {}".format(child_name)
435                    )
436            # which exception, shouldn't we catch explicitly .. ?
437            except Exception:  # pylint: disable=broad-except
438                # Good! We are disconnected from a controlling tty.
439                pass
440            tty_fd = os.open(child_name, os.O_RDWR)
441            setwinsize(tty_fd, rows, cols)
442            if tty_fd < 0:
443                raise TerminalException(
444                    "Could not open child pty, {}".format(child_name)
445                )
446            else:
447                os.close(tty_fd)
448            if os.name != "posix":
449                tty_fd = os.open("/dev/tty", os.O_WRONLY)
450                if tty_fd < 0:
451                    raise TerminalException("Could not open controlling tty, /dev/tty")
452                else:
453                    os.close(tty_fd)
454
455            salt.utils.crypt.reinit_crypto()
456
457            if preexec_fn is not None:
458                preexec_fn()
459
460        def _send(self, data):
461            if self.child_fd is None:
462                return None
463
464            if not select.select([], [self.child_fd], [], 0)[1]:
465                return 0
466
467            try:
468                if self.stdin_logger:
469                    self.stdin_logger.log(self.stdin_logger_level, data)
470                written = os.write(self.child_fd, data.encode(__salt_system_encoding__))
471            except OSError as why:
472                if why.errno == errno.EPIPE:  # broken pipe
473                    os.close(self.child_fd)
474                    self.child_fd = None
475                    return
476                raise
477            return written
478
479        def _recv(self, maxsize):
480            rfds = []
481            if self.child_fd:
482                rfds.append(self.child_fd)
483            if self.child_fde:
484                rfds.append(self.child_fde)
485
486            if not self.isalive():
487                if not rfds:
488                    self.close()
489                    return None, None
490                rlist, _, _ = select.select(rfds, [], [], 0)
491                if not rlist:
492                    self.flag_eof_stdout = self.flag_eof_stderr = True
493                    log.debug("End of file(EOL). Brain-dead platform.")
494                    if self.partial_data_stdout or self.partial_data_stderr:
495                        # There is data that was received but for which
496                        # decoding failed, attempt decoding again to generate
497                        # relevant exception
498                        self.close()
499                        return (
500                            salt.utils.stringutils.to_unicode(self.partial_data_stdout),
501                            salt.utils.stringutils.to_unicode(self.partial_data_stderr),
502                        )
503                    self.close()
504                    return None, None
505
506            stderr = ""
507            stdout = ""
508
509            if self.child_fd:
510                fd_flags = fcntl.fcntl(self.child_fd, fcntl.F_GETFL)
511            if self.child_fde:
512                fde_flags = fcntl.fcntl(self.child_fde, fcntl.F_GETFL)
513            if self.child_fd:
514                fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fd_flags | os.O_NONBLOCK)
515            if self.child_fde:
516                fcntl.fcntl(self.child_fde, fcntl.F_SETFL, fde_flags | os.O_NONBLOCK)
517
518            rlist, _, _ = select.select(rfds, [], [], 0)
519
520            if not rlist:
521                if not self.isalive():
522                    self.flag_eof_stdout = self.flag_eof_stderr = True
523                    log.debug("End of file(EOL). Very slow platform.")
524                    return None, None
525
526            def read_and_decode_fd(fd, maxsize, partial_data_attr=None):
527                bytes_read = getattr(self, partial_data_attr, b"")
528                # Only read one byte if we already have some existing data
529                # to try and complete a split multibyte character
530                bytes_read += os.read(fd, maxsize if not bytes_read else 1)
531                try:
532                    decoded_data = self._translate_newlines(
533                        salt.utils.stringutils.to_unicode(
534                            bytes_read, self.receive_encoding
535                        )
536                    )
537                    if partial_data_attr is not None:
538                        setattr(self, partial_data_attr, b"")
539                    return decoded_data, False
540                except UnicodeDecodeError as ex:
541                    max_multibyte_character_length = 4
542                    if ex.start > (
543                        len(bytes_read) - max_multibyte_character_length
544                    ) and ex.end == len(bytes_read):
545                        # We weren't able to decode the received data possibly
546                        # because it is a multibyte character split across
547                        # blocks. Save what data we have to try and decode
548                        # later. If the error wasn't caused by a multibyte
549                        # character being split then the error start position
550                        # should remain the same each time we get here but the
551                        # length of the bytes_read will increase so we will
552                        # give up and raise an exception instead.
553                        if partial_data_attr is not None:
554                            setattr(self, partial_data_attr, bytes_read)
555                        else:
556                            # We haven't been given anywhere to store partial
557                            # data so raise the exception instead
558                            raise
559                        # No decoded data to return, but indicate that there
560                        # is buffered data
561                        return "", True
562                    else:
563                        raise
564
565            if self.child_fde in rlist and not self.flag_eof_stderr:
566                try:
567                    stderr, partial_data = read_and_decode_fd(
568                        self.child_fde, maxsize, "partial_data_stderr"
569                    )
570
571                    if not stderr and not partial_data:
572                        self.flag_eof_stderr = True
573                        stderr = None
574                    else:
575                        if self.stream_stderr:
576                            self.stream_stderr.write(stderr)
577                            self.stream_stderr.flush()
578
579                        if self.stderr_logger:
580                            stripped = stderr.rstrip()
581                            if stripped.startswith(os.linesep):
582                                stripped = stripped[len(os.linesep) :]
583                            if stripped:
584                                self.stderr_logger.log(
585                                    self.stderr_logger_level, stripped
586                                )
587                except OSError:
588                    os.close(self.child_fde)
589                    self.child_fde = None
590                    self.flag_eof_stderr = True
591                    stderr = None
592                finally:
593                    if self.child_fde is not None:
594                        fcntl.fcntl(self.child_fde, fcntl.F_SETFL, fde_flags)
595
596            if self.child_fd in rlist and not self.flag_eof_stdout:
597                try:
598                    stdout, partial_data = read_and_decode_fd(
599                        self.child_fd, maxsize, "partial_data_stdout"
600                    )
601
602                    if not stdout and not partial_data:
603                        self.flag_eof_stdout = True
604                        stdout = None
605                    else:
606                        if self.stream_stdout:
607                            self.stream_stdout.write(
608                                salt.utils.stringutils.to_str(stdout)
609                            )
610                            self.stream_stdout.flush()
611
612                        if self.stdout_logger:
613                            stripped = stdout.rstrip()
614                            if stripped.startswith(os.linesep):
615                                stripped = stripped[len(os.linesep) :]
616                            if stripped:
617                                self.stdout_logger.log(
618                                    self.stdout_logger_level, stripped
619                                )
620                except OSError:
621                    os.close(self.child_fd)
622                    self.child_fd = None
623                    self.flag_eof_stdout = True
624                    stdout = None
625                finally:
626                    if self.child_fd is not None:
627                        fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fd_flags)
628            # <---- Process STDOUT -------------------------------------------
629            return stdout, stderr
630
631        def __detect_parent_terminal_size(self):
632            try:
633                TIOCGWINSZ = getattr(termios, "TIOCGWINSZ", 1074295912)
634                packed = struct.pack(b"HHHH", 0, 0, 0, 0)
635                ioctl = fcntl.ioctl(sys.stdin.fileno(), TIOCGWINSZ, packed)
636                return struct.unpack(b"HHHH", ioctl)[0:2]
637            except OSError:
638                # Return a default value of 24x80
639                return 24, 80
640
641        # <---- Internal API -------------------------------------------------
642
643        # ----- Public API -------------------------------------------------->
644        def getwinsize(self):
645            """
646            This returns the terminal window size of the child tty. The return
647            value is a tuple of (rows, cols).
648
649            Thank you for the shortcut PEXPECT
650            """
651            if self.child_fd is None:
652                raise TerminalException(
653                    "Can't check the size of the terminal since we're not "
654                    "connected to the child process."
655                )
656            return getwinsize(self.child_fd)
657
658        def setwinsize(self, child, rows=80, cols=80):
659            setwinsize(self.child_fd, rows, cols)
660
661        def isalive(
662            self,
663            _waitpid=os.waitpid,
664            _wnohang=os.WNOHANG,
665            _wifexited=os.WIFEXITED,
666            _wexitstatus=os.WEXITSTATUS,
667            _wifsignaled=os.WIFSIGNALED,
668            _wifstopped=os.WIFSTOPPED,
669            _wtermsig=os.WTERMSIG,
670            _os_error=os.error,
671            _errno_echild=errno.ECHILD,
672            _terminal_exception=TerminalException,
673        ):
674            """
675            This tests if the child process is running or not. This is
676            non-blocking. If the child was terminated then this will read the
677            exitstatus or signalstatus of the child. This returns True if the
678            child process appears to be running or False if not. It can take
679            literally SECONDS for Solaris to return the right status.
680            """
681            if self.terminated:
682                return False
683
684            if self.has_unread_data is False:
685                # This is for Linux, which requires the blocking form
686                # of waitpid to get status of a defunct process.
687                # This is super-lame. The flag_eof_* would have been set
688                # in recv(), so this should be safe.
689                waitpid_options = 0
690            else:
691                waitpid_options = _wnohang
692
693            try:
694                pid, status = _waitpid(self.pid, waitpid_options)
695            except _os_error:
696                err = sys.exc_info()[1]
697                # No child processes
698                if err.errno == _errno_echild:
699                    raise _terminal_exception(
700                        'isalive() encountered condition where "terminated" '
701                        "is 0, but there was no child process. Did someone "
702                        "else call waitpid() on our process?"
703                    )
704                else:
705                    raise
706
707            # I have to do this twice for Solaris.
708            # I can't even believe that I figured this out...
709            # If waitpid() returns 0 it means that no child process
710            # wishes to report, and the value of status is undefined.
711            if pid == 0:
712                try:
713                    ### os.WNOHANG # Solaris!
714                    pid, status = _waitpid(self.pid, waitpid_options)
715                except _os_error as exc:
716                    # This should never happen...
717                    if exc.errno == _errno_echild:
718                        raise _terminal_exception(
719                            "isalive() encountered condition that should "
720                            "never happen. There was no child process. Did "
721                            "someone else call waitpid() on our process?"
722                        )
723                    else:
724                        raise
725
726                # If pid is still 0 after two calls to waitpid() then the
727                # process really is alive. This seems to work on all platforms,
728                # except for Irix which seems to require a blocking call on
729                # waitpid or select, so I let recv take care of this situation
730                # (unfortunately, this requires waiting through the timeout).
731                if pid == 0:
732                    return True
733
734            if pid == 0:
735                return True
736
737            if _wifexited(status):
738                self.status = status
739                self.exitstatus = _wexitstatus(status)
740                self.signalstatus = None
741                self.terminated = True
742            elif _wifsignaled(status):
743                self.status = status
744                self.exitstatus = None
745                self.signalstatus = _wtermsig(status)
746                self.terminated = True
747            elif _wifstopped(status):
748                raise _terminal_exception(
749                    "isalive() encountered condition where child process is "
750                    "stopped. This is not supported. Is some other process "
751                    "attempting job control with our child pid?"
752                )
753            return False
754
755        def terminate(self, force=False):
756            """
757            This forces a child process to terminate. It starts nicely with
758            SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
759            returns True if the child was terminated. This returns False if the
760            child could not be terminated.
761            """
762            if not self.closed:
763                self.close(terminate=False)
764
765            if not self.isalive():
766                return True
767            try:
768                self.send_signal(signal.SIGHUP)
769                time.sleep(0.1)
770                if not self.isalive():
771                    return True
772                self.send_signal(signal.SIGCONT)
773                time.sleep(0.1)
774                if not self.isalive():
775                    return True
776                self.send_signal(signal.SIGINT)
777                time.sleep(0.1)
778                if not self.isalive():
779                    return True
780                if force:
781                    self.send_signal(signal.SIGKILL)
782                    time.sleep(0.1)
783                    if not self.isalive():
784                        return True
785                    else:
786                        return False
787                return False
788            except OSError:
789                # I think there are kernel timing issues that sometimes cause
790                # this to happen. I think isalive() reports True, but the
791                # process is dead to the kernel.
792                # Make one last attempt to see if the kernel is up to date.
793                time.sleep(0.1)
794                if not self.isalive():
795                    return True
796                else:
797                    return False
798
799        def wait(self):
800            """
801            This waits until the child exits internally consuming any remaining
802            output from the child, thus, no blocking forever because the child
803            has unread data.
804            """
805            if self.isalive():
806                while self.isalive():
807                    stdout, stderr = self.recv()
808                    if stdout is None:
809                        break
810                    if stderr is None:
811                        break
812            else:
813                raise TerminalException("Cannot wait for dead child process.")
814            return self.exitstatus
815
816        def send_signal(self, sig):
817            """
818            Send a signal to the process
819            """
820            os.kill(self.pid, sig)
821
822        def kill(self):
823            """
824            Kill the process with SIGKILL
825            """
826            self.send_signal(signal.SIGKILL)
827