-
-
Notifications
You must be signed in to change notification settings - Fork 34.7k
bpo-41494: Adds window resizing support to pty.spawn [ SIGWINCH ] #21752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| """Pseudo terminal utilities.""" | ||
|
|
||
| # Bugs: No signal handling. Doesn't set slave termios and window size. | ||
| # Bugs: No signal handling. Doesn't set slave termios. | ||
| # Only tested on Linux. | ||
| # See: W. Richard Stevens. 1992. Advanced Programming in the | ||
| # UNIX Environment. Chapter 19. | ||
|
|
@@ -10,8 +10,13 @@ | |
| import os | ||
| import sys | ||
| import tty | ||
| import signal | ||
| import struct | ||
| import fcntl | ||
| import termios | ||
| import time | ||
|
|
||
| __all__ = ["openpty","fork","spawn"] | ||
| __all__ = ["openpty","fork","spawn","wspawn"] | ||
|
|
||
| STDIN_FILENO = 0 | ||
| STDOUT_FILENO = 1 | ||
|
|
@@ -170,3 +175,128 @@ def spawn(argv, master_read=_read, stdin_read=_read): | |
|
|
||
| os.close(master_fd) | ||
| return os.waitpid(pid, 0)[1] | ||
|
|
||
| # Author: Soumendra Ganguly. | ||
| SIGWINCH = signal.SIGWINCH | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| def _login_pty(master_fd, slave_fd): | ||
| """Given a pty, makes the calling process a session leader, makes the pty slave | ||
| its controlling terminal, stdin, stdout, and stderr. Closes both pty ends.""" | ||
| # Establish a new session. | ||
| os.setsid() | ||
| os.close(master_fd) | ||
|
|
||
| try: | ||
| fcntl.ioctl(slave_fd, termios.TIOCSCTTY) # Make the pty slave the controlling terminal. | ||
| except: | ||
| os.close(slave_fd) | ||
| raise | ||
|
|
||
| # Slave becomes stdin/stdout/stderr. | ||
| 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) | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C code equivalent to "_winresz" is Notice that the above code checks if the TIOCGWINSZ call is a success before References: |
||
| def _winresz(pty_slave): | ||
| """Resize window.""" | ||
| w = struct.pack('HHHH', 0, 0, 0, 0) | ||
| s = fcntl.ioctl(STDIN_FILENO, termios.TIOCGWINSZ, w) | ||
| fcntl.ioctl(pty_slave, termios.TIOCSWINSZ, s) | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The SIGWINCH handler depends on the pty slave fd returned by openpty in the body of "wspawn" [ see below ]; while the handler could just have been a function nested inside wspawn, that would make wspawn less readable. That is the reason for writing "_create_hwinch", which simply takes the pty slave fd as an argument and returns the appropriate signal handler function [ for SIGWINCH ]. References: |
||
| def _create_hwinch(pty_slave): | ||
| """Creates SIGWINCH handler.""" | ||
| def _hwinch(signum, frame): | ||
| try: | ||
| _winresz(pty_slave) | ||
| except: | ||
| pass | ||
| return _hwinch | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C code equivalent to "_cleanup" would use close(2), tcsetattr(3), and sigaction(2). This performs cleanup such as closing files, resetting tty attributes, and resetting the SIGWINCH signal handler. It is called right before wspawn returns/raises an exception. References:
|
||
| def _cleanup(master_fd, slave_fd, old_hwinch, tty_mode): | ||
| """Performs cleanup in wspawn.""" | ||
|
|
||
| # Close both pty ends. | ||
| os.close(master_fd) | ||
| os.close(slave_fd) | ||
|
|
||
| # Restore original tty attributes. | ||
| if tty_mode != None: | ||
| tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, tty_mode) | ||
|
|
||
| # Restore original SIGWINCH signal handler. | ||
| try: | ||
| signal.signal(SIGWINCH, old_hwinch) | ||
| except: | ||
| pass | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This paragraph explains "_ekill". If the parent loop [ see "_wcopy" below ] terminates due to an exception, then that exception is caught by wspawn. Then, we call waitpid on spawned child process so that it does not become a zombie. However, right before calling waitpid, the child process is sent SIGTERM. A second (*) after that, the child process, if alive, receives SIGKILL to ensure that it is no longer running. (*) Will an adjustable sleep be more useful? References: |
||
| def _ekill(child_pid): | ||
| """Kill spawned process due to exception.""" | ||
| try: | ||
| os.kill(child_pid, signal.SIGTERM) | ||
| time.sleep(1) | ||
| os.kill(child_pid, signal.SIGKILL) | ||
| except: | ||
| pass | ||
| try: | ||
| os.waitpid(child_pid, 0) | ||
| except: | ||
| pass | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a modified version of "_copy". The differences are:
References: |
||
| def _wcopy(master_fd, child_pid, master_read=_read, stdin_read=_read, timeout=0.01): | ||
| """Parent copy loop for wspawn.""" | ||
| fds = [master_fd, STDIN_FILENO] | ||
| ret = (0,0) | ||
| while True: | ||
| rfds, wfds, xfds = select(fds, [], [], timeout) | ||
| if ret == (0,0): | ||
| ret = os.waitpid(child_pid, os.WNOHANG) | ||
| elif len(rfds) == 0: | ||
| break; | ||
| if master_fd in rfds: | ||
| data = master_read(master_fd) | ||
| if not data: # Reached EOF. | ||
| fds.remove(master_fd) | ||
| else: | ||
| os.write(STDOUT_FILENO, data) | ||
| if STDIN_FILENO in rfds: | ||
| data = stdin_read(STDIN_FILENO) | ||
| if not data: | ||
| fds.remove(STDIN_FILENO) | ||
| else: | ||
| _writen(master_fd, data) | ||
| return ret | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "wspawn" is spawn+the following differences.
No. 3 above is related to |
||
| def wspawn(argv, master_read=_read, stdin_read=_read, timeout=0.01): | ||
| """Create a spawned process with | ||
| terminal window resizing enabled.""" | ||
| if type(argv) == type(''): | ||
| argv = (argv,) | ||
| sys.audit('pty.wspawn', argv) | ||
| bk_hwinch = signal.getsignal(SIGWINCH) | ||
| master_fd, slave_fd = openpty() | ||
| try: | ||
| _winresz(slave_fd) | ||
| signal.signal(SIGWINCH, _create_hwinch(slave_fd)) | ||
| except: # User should handle exception and try spawn instead. | ||
| _cleanup(master_fd, slave_fd, bk_hwinch, None) | ||
| raise | ||
| pid = os.fork() | ||
| if pid == CHILD: | ||
| _login_pty(master_fd, slave_fd) | ||
| os.execlp(argv[0], *argv) | ||
| try: | ||
| mode = tty.tcgetattr(STDIN_FILENO) | ||
| tty.setraw(STDIN_FILENO) | ||
| except tty.error: # This is the same as termios.error | ||
| mode = None | ||
| try: | ||
| ret = _wcopy(master_fd, pid, master_read, stdin_read, timeout)[1] | ||
| except: | ||
| _ekill(pid) | ||
| _cleanup(master_fd, slave_fd, bk_hwinch, mode) | ||
| raise | ||
|
|
||
| _cleanup(master_fd, slave_fd, bk_hwinch, mode) | ||
| return ret |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Adds pty.wspawn, which is like pty.spawn + window resizing support [ handles SIGWINCH ]. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding detailed comments for the reviewers. Most manpages referenced are from Linux. However, they should be similar on the BSDs.
Bugs: