Open a connection to FORM.

        Open a connection to a new FORM process. The opened connection should
        be closed by :meth:`close`, which can be guaranteed by use of the
        "with" statement:

        >>> import form
        >>> with form.open() as formlink:
        ...     pass  # use formlink ...

        If this method is called for a link object that has an established
        connection to a FORM process, then the existing connection will be
        closed and a new connection will be created.

        The optional argument ``args`` is for the FORM command, a string or
        a sequence of strings. For example '/path/to/form' or ['tform', '-w4'].
        By default, the value of the environment variable ``$FORM`` is used if
        set, otherwise 'form' will be used.

        The other argument ``keep_log`` indicates whether the log from FORM is
        kept and used as detailed information when an error occurs.
        If the value is >= 2, it specifies the maximum number of lines for
        the scrollback. The default value is False.

        Note
        ----
        In the current implementation, ``keep_log=True`` may cause a dead lock
        when the listing of the input is enabled and very long input is sent to
        FORM.

def open(self, args=None, keep_log=False):
        # type: (Optional[Union[str, Sequence[str]]], Union[bool, int]) -> None
        """Open a connection to FORM.

        Open a connection to a new FORM process. The opened connection should
        be closed by :meth:`close`, which can be guaranteed by use of the
        "with" statement:

        >>> import form
        >>> with form.open() as formlink:
        ...     pass  # use formlink ...

        If this method is called for a link object that has an established
        connection to a FORM process, then the existing connection will be
        closed and a new connection will be created.

        The optional argument ``args`` is for the FORM command, a string or
        a sequence of strings. For example '/path/to/form' or ['tform', '-w4'].
        By default, the value of the environment variable ``$FORM`` is used if
        set, otherwise 'form' will be used.

        The other argument ``keep_log`` indicates whether the log from FORM is
        kept and used as detailed information when an error occurs.
        If the value is >= 2, it specifies the maximum number of lines for
        the scrollback. The default value is False.

        Note
        ----
        In the current implementation, ``keep_log=True`` may cause a dead lock
        when the listing of the input is enabled and very long input is sent to
        FORM.
        """
        if args is None:
            if 'FORM' in os.environ:
                args = os.environ['FORM']
            else:
                args = 'form'
        if isinstance(args, string_types):
            args = shlex.split(args)  # Split the arguments.
        elif isinstance(args, (list, tuple)):
            args = list(args)  # As a modifiable mutable object.
        else:
            raise TypeError("invalid args = {0}".format(args))

        self.close()

        fd_childin, fd_parentout = os.pipe()
        fd_parentin, fd_childout = os.pipe()
        fd_loggingin, fd_loggingout = os.pipe()

        pid = os.fork()
        if pid:
            # parent process
            os.close(fd_childin)
            os.close(fd_childout)
            os.close(fd_loggingout)

            parentin = os.fdopen(fd_parentin, 'r')
            parentout = os.fdopen(fd_parentout, 'w')
            loggingin = os.fdopen(fd_loggingin, 'r')

            # FORM sends 'pid\n'.
            s = parentin.readline()
            if not s:
                os.waitpid(pid, 0)
                parentin.close()
                parentout.close()
                loggingin.close()
                raise IOError('failed to read the first line from FORM')
            s = s.rstrip()
            formpid = int(s)
            # The parent must send 'pid,ppid\n'.
            s = s + ',{0}\n'.format(os.getpid())
            parentout.write(s)
            parentout.flush()
            # FORM sends 'OK' (in init.frm).
            s = parentin.read(2)
            if s != 'OK':
                os.waitpid(pid, 0)
                parentin.close()
                parentout.close()
                loggingin.close()
                raise IOError('failed to establish the connection to FORM')
            # Change the prompt.
            parentout.write('#prompt {0}\n'.format(self._PROMPT.strip()))
            # Read the first line of the FORM output.
            head = loggingin.readline().rstrip()

            set_nonblock(fd_parentin)
            set_nonblock(fd_loggingin)

            self._closed = False
            self._head = head
            if keep_log:
                if keep_log >= 2:
                    log = collections.deque(maxlen=keep_log)  # type: Any
                    self._log = log  # hack typeshed for Python 2
                    assert self._log is not None
                    self._log.append(head)
                else:
                    self._log = []
                    self._log.append(head)
            else:
                self._log = None
                # Turn off the listing of the input.
                parentout.write('#-\n')
            self._childpid = pid
            self._formpid = formpid
            self._parentin = PushbackReader(parentin)
            self._parentout = parentout
            self._loggingin = PushbackReader(loggingin)
        else:
            # child process

            # NOTE: Coverage fails to collect data from child processes when
            #       the os.fork+os._exit pattern is used.
            #       https://bitbucket.org/ned/coveragepy/issues/310

            os.close(fd_parentout)
            os.close(fd_parentin)
            os.close(fd_loggingin)
            os.dup2(fd_loggingout, sys.__stdout__.fileno())

            args.append('-M')
            args.append('-pipe')
            args.append('{0},{1}'.format(fd_childin, fd_childout))
            args.append(FormLink._INIT_FRM)

            # In Python 3.2, subprocess.Popen() on UNIX changed the default
            # value for close_fds from False to True, in order to stop leaking
            # file descriptors. File descriptors to be kept open should be
            # specified by pass_fds.
            if sys.version_info[0:2] < (3, 2):
                subprocess.call(args, shell=False)
            else:
                subprocess.call(args, shell=False,
                                pass_fds=(fd_childin,
                                          fd_childout,
                                          fd_loggingout))

            os.close(fd_childin)
            os.close(fd_childout)
            os.close(fd_loggingout)
            os._exit(0)