python pty.fork - how does it work

webminal.org picture webminal.org · Oct 26, 2010 · Viewed 12.9k times · Source

http://docs.python.org/library/pty.html says -

pty.fork()¶ Fork. Connect the child’s controlling terminal to a pseudo-terminal. Return value is (pid, fd). Note that the child gets pid 0, and the fd is invalid. The parent’s return value is the pid of the child, and fd is a file descriptor connected to the child’s controlling terminal (and also to the child’s standard input and output).

What's does this mean ? Every process has 3 fd (stdin,stdout,stderr). Does this affects these fds now ? will child process won't have any of these fds? I'm confused.--totally.

Answer

sdaau picture sdaau · Aug 5, 2011

I think I finally got a minimal example for pty.fork in Python - and since I found it extremely difficult to find a similar example, I'm posting it here as an illustration of @joni's answer. It is essentially based on:

Particularly nasty bits are finding documentation that still refers to master_open() which is obsolete; and the fact that pty.fork will not spawn a child process, unless the file descriptor (returned by the fork method) is read from by the parent process! (note that in os.fork there is no such requirement) Also, it seems that os.fork is a bit more portable (read a few comments noting that pty.fork doesn't work on some platforms).

Anyways, here's first a script (pyecho.py) that acts as an executable (it simply reads lines from standard input, and writes them back in uppercase):

#!/usr/bin/env python
# pyecho.py

import sys;

print "pyecho starting..."

while True:
  print sys.stdin.readline().upper()

... and then, here is the actual script (it will require that pyecho.py is in the same directory):

#!/usr/bin/env python

import sys
import os
import time
import pty

def my_pty_fork():

  # fork this script
  try:
    ( child_pid, fd ) = pty.fork()    # OK
    #~ child_pid, fd = os.forkpty()      # OK
  except OSError as e:
    print str(e)

  #~ print "%d - %d" % (fd, child_pid)
  # NOTE - unlike OS fork; in pty fork we MUST use the fd variable
  #   somewhere (i.e. in parent process; it does not exist for child)
  # ... actually, we must READ from fd in parent process...
  #   if we don't - child process will never be spawned!

  if child_pid == 0:
    print "In Child Process: PID# %s" % os.getpid()
    # note: fd for child is invalid (-1) for pty fork!
    #~ print "%d - %d" % (fd, child_pid)

    # the os.exec replaces the child process
    sys.stdout.flush()
    try:
      #Note: "the first of these arguments is passed to the new program as its own name"
      # so:: "python": actual executable; "ThePythonProgram": name of executable in process list (`ps axf`); "pyecho.py": first argument to executable..
      os.execlp("python","ThePythonProgram","pyecho.py")
    except:
      print "Cannot spawn execlp..."
  else:
    print "In Parent Process: PID# %s" % os.getpid()
    # MUST read from fd; else no spawn of child!
    print os.read(fd, 100) # in fact, this line prints out the "In Child Process..." sentence above!

    os.write(fd,"message one\n")
    print os.read(fd, 100)        # message one
    time.sleep(2)
    os.write(fd,"message two\n")
    print os.read(fd, 10000)      # pyecho starting...\n MESSAGE ONE
    time.sleep(2)
    print os.read(fd, 10000)      # message two \n MESSAGE TWO
    # uncomment to lock (can exit with Ctrl-C)
    #~ while True:
      #~ print os.read(fd, 10000)


if __name__ == "__main__":
    my_pty_fork()

Well, hope this helps someone,
Cheers!