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