SSH and telnet library
[monitor.git] / pyssh / ptyext.py
diff --git a/pyssh/ptyext.py b/pyssh/ptyext.py
new file mode 100644 (file)
index 0000000..b22835b
--- /dev/null
@@ -0,0 +1,209 @@
+"""Pseudo terminal utilities."""\r
+\r
+# Bugs: No signal handling.  Doesn't set slave termios and window size.\r
+#       Only tested on Linux.\r
+# See:  W. Richard Stevens. 1992.  Advanced Programming in the\r
+#       UNIX Environment.  Chapter 19.\r
+# Author: Steen Lumholt -- with additions by Guido.\r
+\r
+from select import select, error\r
+import os\r
+\r
+# Absurd:  import termios and then delete it.  This is to force an attempt\r
+# to import pty to raise an ImportError on platforms that lack termios.\r
+# Without this explicit import of termios here, some other module may\r
+# import tty first, which in turn imports termios and dies with an\r
+# ImportError then.  But since tty *does* exist across platforms, that\r
+# leaves a damaged module object for tty in sys.modules, and the import\r
+# of tty here then appears to work despite that the tty imported is junk.\r
+import termios\r
+del termios\r
+\r
+import tty\r
+\r
+__all__ = ["openpty","fork","spawn","th_spawn","popen2"]\r
+\r
+STDIN_FILENO = 0\r
+STDOUT_FILENO = 1\r
+STDERR_FILENO = 2\r
+\r
+CHILD = 0\r
+\r
+def openpty():\r
+    """openpty() -> (master_fd, slave_fd)\r
+    Open a pty master/slave pair, using os.openpty() if possible."""\r
+\r
+    try:\r
+        return os.openpty()\r
+    except (AttributeError, OSError):\r
+        pass\r
+    master_fd, slave_name = _open_terminal()\r
+    slave_fd = slave_open(slave_name)\r
+    return master_fd, slave_fd\r
+\r
+def master_open():\r
+    """master_open() -> (master_fd, slave_name)\r
+    Open a pty master and return the fd, and the filename of the slave end.\r
+    Deprecated, use openpty() instead."""\r
+\r
+    try:\r
+        master_fd, slave_fd = os.openpty()\r
+    except (AttributeError, OSError):\r
+        pass\r
+    else:\r
+        slave_name = os.ttyname(slave_fd)\r
+        os.close(slave_fd)\r
+        return master_fd, slave_name\r
+\r
+    return _open_terminal()\r
+\r
+def _open_terminal():\r
+    """Open pty master and return (master_fd, tty_name).\r
+    SGI and generic BSD version, for when openpty() fails."""\r
+    try:\r
+        import sgi\r
+    except ImportError:\r
+        pass\r
+    else:\r
+        try:\r
+            tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)\r
+        except IOError, msg:\r
+            raise os.error, msg\r
+        return master_fd, tty_name\r
+    for x in 'pqrstuvwxyzPQRST':\r
+        for y in '0123456789abcdef':\r
+            pty_name = '/dev/pty' + x + y\r
+            try:\r
+                fd = os.open(pty_name, os.O_RDWR)\r
+            except os.error:\r
+                continue\r
+            return (fd, '/dev/tty' + x + y)\r
+    raise os.error, 'out of pty devices'\r
+\r
+def slave_open(tty_name):\r
+    """slave_open(tty_name) -> slave_fd\r
+    Open the pty slave and acquire the controlling terminal, returning\r
+    opened filedescriptor.\r
+    Deprecated, use openpty() instead."""\r
+\r
+    return os.open(tty_name, os.O_RDWR)\r
+\r
+def fork():\r
+    """fork() -> (pid, master_fd)\r
+    Fork and make the child a session leader with a controlling terminal."""\r
+\r
+    try:\r
+        pid, fd = os.forkpty()\r
+    except (AttributeError, OSError):\r
+        pass\r
+    else:\r
+        if pid == CHILD:\r
+            try:\r
+                os.setsid()\r
+            except OSError:\r
+                # os.forkpty() already set us session leader\r
+                pass\r
+        return pid, fd\r
+\r
+    master_fd, slave_fd = openpty()\r
+    pid = os.fork()\r
+    if pid == CHILD:\r
+        # Establish a new session.\r
+        os.setsid()\r
+        os.close(master_fd)\r
+\r
+        # Slave becomes stdin/stdout/stderr of child.\r
+        os.dup2(slave_fd, STDIN_FILENO)\r
+        os.dup2(slave_fd, STDOUT_FILENO)\r
+        os.dup2(slave_fd, STDERR_FILENO)\r
+        if (slave_fd > STDERR_FILENO):\r
+            os.close (slave_fd)\r
+\r
+    # Parent and child process.\r
+    return pid, master_fd\r
+\r
+def _writen(fd, data):\r
+    """Write all the data to a descriptor."""\r
+    while data != '':\r
+        n = os.write(fd, data)\r
+        data = data[n:]\r
+\r
+def _read(fd):\r
+    """Default read function."""\r
+    return os.read(fd, 1024)\r
+\r
+def _copy(master_fd, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
+             stdout_fd=STDOUT_FILENO):\r
+    """Parent copy loop.\r
+    Copies\r
+            pty master -> stdout_fd     (master_read)\r
+            stdin_fd   -> pty master    (stdin_read)"""\r
+    try:\r
+        mode = tty.tcgetattr(stdin_fd)\r
+        tty.setraw(stdin_fd)\r
+        restore = 1\r
+    except tty.error:    # This is the same as termios.error\r
+        restore = 0\r
+    try:\r
+        while 1:\r
+            rfds, wfds, xfds = select(\r
+                    [master_fd, stdin_fd], [], [])\r
+            if master_fd in rfds:\r
+                data = master_read(master_fd)\r
+                os.write(stdout_fd, data)\r
+            if stdin_fd in rfds:\r
+                data = stdin_read(stdin_fd)\r
+                _writen(master_fd, data)\r
+    except (IOError, OSError, error):  # The last entry is select.error\r
+        if restore:\r
+            tty.tcsetattr(stdin_fd, tty.TCSAFLUSH, mode)\r
+        if stdin_fd > STDERR_FILENO:\r
+            os.close(stdin_fd)\r
+        if stdout_fd > STDERR_FILENO:\r
+            os.close(stdout_fd)\r
+\r
+def spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
+            stdout_fd=STDOUT_FILENO):\r
+    """Create a spawned process. The controlling terminal reads and\r
+    writes its data to stdin_fd and stdout_fd respectively.\r
+    \r
+    NOTE: This function does not return until one of the input or output file\r
+    descriptors are closed, or the child process exits."""\r
+    if type(argv) == type(''):\r
+        argv = (argv,)\r
+    pid, master_fd = fork()\r
+    if pid == CHILD:\r
+        apply(os.execlp, (argv[0],) + argv)\r
+    _copy(master_fd, master_read, stdin_read, stdin_fd, stdout_fd)\r
+\r
+def th_spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
+                stdout_fd=STDOUT_FILENO):\r
+    """Create a spawned process. The controlling terminal reads and\r
+    writes its data to stdin_fd and stdout_fd respectively. The function\r
+    returns the pid of the spawned process.  (It returns immediately.)"""\r
+    import thread\r
+    if type(argv) == type(''):\r
+        argv = (argv,)\r
+    pid, master_fd = fork()\r
+    if pid == CHILD:\r
+        apply(os.execlp, (argv[0],) + argv)\r
+    thread.start_new_thread(_copy, (master_fd, master_read, stdin_read, \\r
+                                            stdin_fd, stdout_fd))\r
+    return pid\r
+\r
+def popen2(cmd, bufsize=1024, master_read=_read, stdin_read=_read):\r
+    """Execute the shell command 'cmd' in a sub-process.\r
+    \r
+    If 'bufsize' is specified, it sets the buffer size for the I/O pipes.\r
+    The function returns (child_stdin, child_stdout, child_pid), where the\r
+    file objects are pipes connected to the spawned process's controling\r
+    terminal, and the child_pid is the pid of the child process.\r
+    """\r
+    argv = ('/bin/sh', '-c', cmd)\r
+    child_stdin_rfd, child_stdin_wfd = os.pipe()\r
+    child_stdout_rfd, child_stdout_wfd = os.pipe()\r
+    child_pid = th_spawn(argv, master_read, stdin_read, child_stdin_rfd, \\r
+                            child_stdout_wfd)\r
+    child_stdin = os.fdopen(child_stdin_wfd, 'w', bufsize)\r
+    child_stdout = os.fdopen(child_stdout_rfd, 'r', bufsize)\r
+    return child_stdin, child_stdout, child_pid\r