Pythonで学ぶ「詳解 UNIXプログラミング」(その16) 第19章 擬似端末

19.1 〜 19.3

Pythonpty.fork()でptyのオープンとforkを行うことができる。pty.fork()はこのサンプルと同等の機能が実装されており、Linux/BSD/Solaris/Cygwin等の各プラットフォーム対応のコードが入っている。ただし、子プロセス側でのtermioとウィンドウサイズの設定は行われない。

19.4 ptyプログラムのmain関数

import os, sys, atexit, termios, pty, socket
from optparse import OptionParser

STDIN_FILENO = 0
STDOUT_FILENO = 1

def main():
    parser = OptionParser()
    parser.add_option("-d", dest="driver", help="driver for stdin/stdout")
    parser.add_option("-e", dest="noecho", action="store_true",
        help="necho for slave pty's line discipline")
    parser.add_option("-i", dest="ignoreeof", action="store_true",
        help="ignore EOF on standard input")
    parser.add_option("-n", dest="interactive", action="store_false",
        default=os.isatty(STDIN_FILENO), help="not interactive")
    
    (options, args) = parser.parse_args()

    pid, fdm = pty.fork()
    if not pid:	# child
        if options.noecho:
            set_noecho(STDIN_FILENO)	# stdin is slave pty
        os.execvp(args[0], args)
    
    # parent
    if options.interactive and not options.driver:
        tty_raw(STDIN_FILENO)	# user's tty to raw mode
        atexit.register(tty_exit)	# reset user's tty on exit
    
    if options.driver:
        do_driver(options.driver)	# changes our stdin/stdout
    
    loop(fdm, options.ignoreeof)	# copies stdin -> ptym,  ptym -> stdout

def set_noecho(fd):
    # turn echo off
    sterm = termios.tcgetattr(fd)
    sterm[2] &= ~(termios.ECHO | termios.ECHOE | termios.ECHOK | termios.ECHONL)
    sterm[1] &= ~(termios.ONLCR) # also turn off NL to CR/NL mapping on output

    termios.tcsetattr(fd, termios.TCSANOW, sterm)

if __name__ == '__main__':
    main()

19.5 loop関数

BUFFSIZE = 512
sigcaught = 0

def loop(ptym, ignoreeof):
    child = os.fork()
    if child == 0:	# child
        # child copies stdin to ptym
        while True:
            try:
                buff = os.read(STDIN_FILENO, BUFFSIZE)
            except OSError:
                break
            if not buff:	# EOF on stdin means we're done
                break
            n = os.write(ptym, buff)
        
        # We always terminate when we encounter an EOF on stdin,
        # but we anly notify the parent if ignoreeof is 0
        if not ignoreeof:
            os.kill(os.getppid(), signal.SIGTERM)	# notify parent
        
    # parent copies ptym to stdout
    signal.signal(signal.SIGTERM, sig_term)
    while True:
        try:
            buff = os.read(ptym, BUFFSIZE)
        except OSError, e:
            break
        if not buff:
            break
        os.write(STDOUT_FILENO, buff)

    # There are three ways to get here: sig_term() below caught the
    # SIGTERM from the child, we read an EOF on the pty master (which
    # means we have to signal the child to stop), or an error.
    
    if not sigcaught:
        os.kill(child, signal.SIGTERM)
    
# The child sends us a SIGTERM when it receives an EOF on
# the pty slave or encounter a read() error.
def sig_term(signo, frame):
    global sigcaught
    sigcaught = 1

19.6 ptyプログラム用のdo_driver関数

def do_driver(driver):
    # create a stream pipe to communicate with the driver
    pipe = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
    child = os.fork()
    if child == 0:	# child
        pipe[1].close()
        
        os.dup2(pipe[0].fileno(), 0)	# stdin for driver
        os.dup2(pipe[0].fileno(), 1)	# stdout for driver
        pipe[0].close()
        
        os.execlp(driver, driver)
    
    pipe[0].close()
    
    os.dup2(pipe[1].fileno(), 0)
    os.dup2(pipe[1].fileno(), 1)
    pipe[1].close()
    
    # Parent returns, but with stdin and stdout connected
    # to the driver.