move pcucontrol package into pcucontrol module.
[pcucontrol.git] / pcucontrol / transports / pyssh / ptyext.py
1 """Pseudo terminal utilities."""\r
2 \r
3 # Bugs: No signal handling.  Doesn't set slave termios and window size.\r
4 #       Only tested on Linux.\r
5 # See:  W. Richard Stevens. 1992.  Advanced Programming in the\r
6 #       UNIX Environment.  Chapter 19.\r
7 # Author: Steen Lumholt -- with additions by Guido.\r
8 \r
9 from select import select, error\r
10 import os\r
11 \r
12 # Absurd:  import termios and then delete it.  This is to force an attempt\r
13 # to import pty to raise an ImportError on platforms that lack termios.\r
14 # Without this explicit import of termios here, some other module may\r
15 # import tty first, which in turn imports termios and dies with an\r
16 # ImportError then.  But since tty *does* exist across platforms, that\r
17 # leaves a damaged module object for tty in sys.modules, and the import\r
18 # of tty here then appears to work despite that the tty imported is junk.\r
19 import termios\r
20 del termios\r
21 \r
22 import tty\r
23 \r
24 __all__ = ["openpty","fork","spawn","th_spawn","popen2"]\r
25 \r
26 STDIN_FILENO = 0\r
27 STDOUT_FILENO = 1\r
28 STDERR_FILENO = 2\r
29 \r
30 CHILD = 0\r
31 \r
32 def openpty():\r
33     """openpty() -> (master_fd, slave_fd)\r
34     Open a pty master/slave pair, using os.openpty() if possible."""\r
35 \r
36     try:\r
37         return os.openpty()\r
38     except (AttributeError, OSError):\r
39         pass\r
40     master_fd, slave_name = _open_terminal()\r
41     slave_fd = slave_open(slave_name)\r
42     return master_fd, slave_fd\r
43 \r
44 def master_open():\r
45     """master_open() -> (master_fd, slave_name)\r
46     Open a pty master and return the fd, and the filename of the slave end.\r
47     Deprecated, use openpty() instead."""\r
48 \r
49     try:\r
50         master_fd, slave_fd = os.openpty()\r
51     except (AttributeError, OSError):\r
52         pass\r
53     else:\r
54         slave_name = os.ttyname(slave_fd)\r
55         os.close(slave_fd)\r
56         return master_fd, slave_name\r
57 \r
58     return _open_terminal()\r
59 \r
60 def _open_terminal():\r
61     """Open pty master and return (master_fd, tty_name).\r
62     SGI and generic BSD version, for when openpty() fails."""\r
63     try:\r
64         import sgi\r
65     except ImportError:\r
66         pass\r
67     else:\r
68         try:\r
69             tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)\r
70         except IOError, msg:\r
71             raise os.error, msg\r
72         return master_fd, tty_name\r
73     for x in 'pqrstuvwxyzPQRST':\r
74         for y in '0123456789abcdef':\r
75             pty_name = '/dev/pty' + x + y\r
76             try:\r
77                 fd = os.open(pty_name, os.O_RDWR)\r
78             except os.error:\r
79                 continue\r
80             return (fd, '/dev/tty' + x + y)\r
81     raise os.error, 'out of pty devices'\r
82 \r
83 def slave_open(tty_name):\r
84     """slave_open(tty_name) -> slave_fd\r
85     Open the pty slave and acquire the controlling terminal, returning\r
86     opened filedescriptor.\r
87     Deprecated, use openpty() instead."""\r
88 \r
89     return os.open(tty_name, os.O_RDWR)\r
90 \r
91 def fork():\r
92     """fork() -> (pid, master_fd)\r
93     Fork and make the child a session leader with a controlling terminal."""\r
94 \r
95     try:\r
96         pid, fd = os.forkpty()\r
97     except (AttributeError, OSError):\r
98         pass\r
99     else:\r
100         if pid == CHILD:\r
101             try:\r
102                 os.setsid()\r
103             except OSError:\r
104                 # os.forkpty() already set us session leader\r
105                 pass\r
106         return pid, fd\r
107 \r
108     master_fd, slave_fd = openpty()\r
109     pid = os.fork()\r
110     if pid == CHILD:\r
111         # Establish a new session.\r
112         os.setsid()\r
113         os.close(master_fd)\r
114 \r
115         # Slave becomes stdin/stdout/stderr of child.\r
116         os.dup2(slave_fd, STDIN_FILENO)\r
117         os.dup2(slave_fd, STDOUT_FILENO)\r
118         os.dup2(slave_fd, STDERR_FILENO)\r
119         if (slave_fd > STDERR_FILENO):\r
120             os.close (slave_fd)\r
121 \r
122     # Parent and child process.\r
123     return pid, master_fd\r
124 \r
125 def _writen(fd, data):\r
126     """Write all the data to a descriptor."""\r
127     while data != '':\r
128         n = os.write(fd, data)\r
129         data = data[n:]\r
130 \r
131 def _read(fd):\r
132     """Default read function."""\r
133     return os.read(fd, 1024)\r
134 \r
135 def _copy(master_fd, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
136              stdout_fd=STDOUT_FILENO):\r
137     """Parent copy loop.\r
138     Copies\r
139             pty master -> stdout_fd     (master_read)\r
140             stdin_fd   -> pty master    (stdin_read)"""\r
141     try:\r
142         mode = tty.tcgetattr(stdin_fd)\r
143         tty.setraw(stdin_fd)\r
144         restore = 1\r
145     except tty.error:    # This is the same as termios.error\r
146         restore = 0\r
147     try:\r
148         while 1:\r
149             rfds, wfds, xfds = select(\r
150                     [master_fd, stdin_fd], [], [])\r
151             if master_fd in rfds:\r
152                 data = master_read(master_fd)\r
153                 os.write(stdout_fd, data)\r
154             if stdin_fd in rfds:\r
155                 data = stdin_read(stdin_fd)\r
156                 _writen(master_fd, data)\r
157     except (IOError, OSError, error):  # The last entry is select.error\r
158         if restore:\r
159             tty.tcsetattr(stdin_fd, tty.TCSAFLUSH, mode)\r
160         if stdin_fd > STDERR_FILENO:\r
161             os.close(stdin_fd)\r
162         if stdout_fd > STDERR_FILENO:\r
163             os.close(stdout_fd)\r
164 \r
165 def spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
166             stdout_fd=STDOUT_FILENO):\r
167     """Create a spawned process. The controlling terminal reads and\r
168     writes its data to stdin_fd and stdout_fd respectively.\r
169     \r
170     NOTE: This function does not return until one of the input or output file\r
171     descriptors are closed, or the child process exits."""\r
172     if type(argv) == type(''):\r
173         argv = (argv,)\r
174     pid, master_fd = fork()\r
175     if pid == CHILD:\r
176         apply(os.execlp, (argv[0],) + argv)\r
177     _copy(master_fd, master_read, stdin_read, stdin_fd, stdout_fd)\r
178 \r
179 def th_spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,\r
180                 stdout_fd=STDOUT_FILENO):\r
181     """Create a spawned process. The controlling terminal reads and\r
182     writes its data to stdin_fd and stdout_fd respectively. The function\r
183     returns the pid of the spawned process.  (It returns immediately.)"""\r
184     import thread\r
185     if type(argv) == type(''):\r
186         argv = (argv,)\r
187     pid, master_fd = fork()\r
188     if pid == CHILD:\r
189         apply(os.execlp, (argv[0],) + argv)\r
190     thread.start_new_thread(_copy, (master_fd, master_read, stdin_read, \\r
191                                             stdin_fd, stdout_fd))\r
192     return pid\r
193 \r
194 def popen2(cmd, bufsize=1024, master_read=_read, stdin_read=_read):\r
195     """Execute the shell command 'cmd' in a sub-process.\r
196     \r
197     If 'bufsize' is specified, it sets the buffer size for the I/O pipes.\r
198     The function returns (child_stdin, child_stdout, child_pid), where the\r
199     file objects are pipes connected to the spawned process's controling\r
200     terminal, and the child_pid is the pid of the child process.\r
201     """\r
202     argv = ('/bin/sh', '-c', cmd)\r
203     child_stdin_rfd, child_stdin_wfd = os.pipe()\r
204     child_stdout_rfd, child_stdout_wfd = os.pipe()\r
205     child_pid = th_spawn(argv, master_read, stdin_read, child_stdin_rfd, \\r
206                             child_stdout_wfd)\r
207     child_stdin = os.fdopen(child_stdin_wfd, 'w', bufsize)\r
208     child_stdout = os.fdopen(child_stdout_rfd, 'r', bufsize)\r
209     return child_stdin, child_stdout, child_pid\r