4 This module contains very UNIX-specific code to allow interactive
5 communication with another program. For CEO they are required to
6 talk to kadmin because there is no Kerberos administration Python
7 module. Real bindings to libkadm5 are doable and thus a TODO.
12 class _pty_file(object):
14 A 'file'-like wrapper class for pseudoterminal file descriptors.
16 This wrapper is necessary because Python has a nasty habit of throwing
19 This class also implements timeouts for read operations which are handy
20 for avoiding deadlock when both processes are blocked in a read().
22 See the Python documentation of the file class for explanation
25 def __init__(self, fd):
33 return "<" + status + " pty '" + os.ttyname(self.fd) + "'>"
34 def read(self, size=-1, block=True, timeout=0.1):
35 if self.closed: raise ValueError
39 # read data, catching OSError as EOF
43 # wait timeout for the pty to become ready, otherwise stop reading
44 if not block and len(select.select([self.fd], [], [], timeout)[0]) == 0:
47 data = os.read(self.fd, 65536)
56 if len(self.buffer) < size:
58 # read data, catching OSError as EOF
61 # wait timeout for the pty to become ready, then read
62 if block or len(select.select([self.fd], [], [], timeout)[0]) != 0:
63 self.buffer += os.read(self.fd, size - len(self.buffer) )
68 data = self.buffer[:size]
69 self.buffer = self.buffer[size:]
71 def readline(self, size=-1, block=True, timeout=0.1):
74 # read data, catching OSError as EOF
76 while data != '' and self.buffer.find("\n") == -1 and (size < 0 or len(self.buffer) < size):
78 # wait timeout for the pty to become ready, otherwise stop reading
79 if not block and len(select.select([self.fd], [], [], timeout)[0]) == 0:
82 data = os.read(self.fd, 128)
87 split_index = self.buffer.find("\n") + 1
89 split_index = len(self.buffer)
90 if size >= 0 and split_index > size:
92 line = self.buffer[:split_index]
93 self.buffer = self.buffer[split_index:]
95 def readlines(self, sizehint=None, timeout=0.1):
99 line = self.readline(-1, False, timeout)
103 def write(self, data):
104 if self.closed: raise ValueError
105 os.write(self.fd, data)
106 def writelines(self, lines):
112 line = self.readline()
117 if self.closed: raise ValueError
118 return os.isatty(self.fd)
120 if self.closed: raise ValueError
123 if self.closed: raise ValueError
126 if not self.closed: os.close(self.fd)
130 def popeni(command, args, env=None):
132 Open an interactive session with another command.
135 command - the command to run (full path)
136 args - a list of arguments to pass to command
137 env - optional environment for command
139 Returns: (pid, stdout, stdin)
142 # use a pipe to send data to the child
143 child_stdin, parent_stdin = os.pipe()
145 # a pipe for receiving data would cause buffering and
146 # is therefore not suitable for interactive communication
147 # i.e. parent_stdout, child_stdout = os.pipe()
149 # therefore a pty must be used instead
150 master, slave = pty.openpty()
152 # collect both stdout and stderr on the pty
153 parent_stdout, child_stdout = master, slave
154 parent_stderr, child_stderr = master, slave
156 # fork the child to communicate with
162 # close all of the parent's fds
163 os.close(parent_stdin)
164 if parent_stdout != parent_stdin:
165 os.close(parent_stdout)
166 if parent_stderr != parent_stdin and parent_stderr != parent_stdout:
167 os.close(parent_stderr)
169 # if stdout is a terminal, set it to the controlling terminal
170 if os.isatty(child_stdout):
172 # determine the filename of the tty
173 tty = os.ttyname(child_stdout)
175 # create a new session to disconnect
176 # from the parent's controlling terminal
179 # set the controlling terminal to the pty
180 # by opening it (and closing it again since
181 # it's already open as child_stdout)
182 fd = os.open(tty, os.O_RDWR)
186 os.dup2(child_stdin, 0)
187 os.dup2(child_stdout, 1)
188 if child_stderr >= 0:
189 os.dup2(child_stderr, 2)
191 # finally, execute the child
193 os.execv(command, args, env)
195 os.execv(command, args)
200 # close all of the child's fds
201 os.close(child_stdin)
202 if child_stdout != child_stdin:
203 os.close(child_stdout)
204 if child_stderr >= 0 and child_stderr != child_stdin and child_stderr != child_stdout:
205 os.close(child_stderr)
207 return pid, _pty_file(parent_stdout), os.fdopen(parent_stdin, 'w')
213 if __name__ == '__main__':
215 from csc.common.test import *
222 proc, recv, send = popeni(prog, argv)
225 line = recv.readline()
226 assert_equal(message.strip(), line.strip())