[Python-Dev] posixmodule.c patch to support forkpty (patch against posixmodule.c Revision 2.241.2.1)

Noah Spurrier noah@noah.org
Sat, 03 May 2003 03:36:53 -0700


This is a multi-part message in MIME format.
--------------020003030503000503080704
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Hi,

I have been taking a hard look at Python 2.3b and support for
pseudo-ttys seems to be much better. It looks like os.openpty() was
updated to provide support for a wider range of pseudo ttys.
Unfortunately os.forkpty() was not also updated.

I am attaching a patch that allows os.forkpty() to run on the
same platforms that os.openpty supports. In other words, os.forkpty()
will use os.fork() and os.openpty() for platforms that don't
already have forkpty(). Note that since pty module calls os.forkpty
this patch will also allow pty.fork() to work properly on more platforms.
Most importantly to me, this patch will allow os.forkpty()
to work with Solaris.

This patch was diffed against posixmodule.c Revision 2.241.2.1 Python 2.3b.

This patch moves most of the logic out of the posix_openpty() C function into
a function that can be shared by both posix_openpty() and posix_forkpty().
Although the posix_openpty() logic was moved it was unchanged. I think I
kept the code neat despite all the messy #if's that always accompany pty code.

I am also attaching a test script, test_forkpty.py (based on test_openpty.py),
that tests the basic ability to fork and read and write a pty.

I am testing it with my Pexpect module which makes heavy use of
the pty module. With the patch Pexpect passes all my unit tests on Solaris.
Pexpect has been tested on Linux, OpenBSD, Solaris, and Cygwin.
I'm looking for an OS X server to test with.

Yours,
Noah

--------------020003030503000503080704
Content-Type: text/plain;
 name="test_forkpty.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="test_forkpty.py"

#!/usr/bin/env python2.3

import os, sys, time
verbose = 1

try:
    if verbose:
        print "Calling os.forkpty()"
    pid, fd = os.forkpty()
    if verbose:
        print "(pid, fd) = (%d, %d)"%(pid, fd)
except AttributeError:
    raise TestSkipped, "No forkpty() available."

if pid == 0: # child
        print "I am not a robot!"
        sys.stdout.flush(0)
else:
        time.sleep(1)
        print "The robot says: ", os.read(fd,100)
        os.close(fd)

--------------020003030503000503080704
Content-Type: text/plain;
 name="posixmodule.c.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="posixmodule.c.patch"

*** posixmodule.c	Tue Apr 22 22:39:17 2003
--- new.posixmodule.c	Sat May  3 06:11:04 2003
***************
*** 2597,2685 ****
  #endif /* defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) || defined(HAVE_DEV_PTMX */
  
  #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)
- PyDoc_STRVAR(posix_openpty__doc__,
- "openpty() -> (master_fd, slave_fd)\n\n\
- Open a pseudo-terminal, returning open fd's for both master and slave end.\n");
- 
  static PyObject *
! posix_openpty(PyObject *self, PyObject *noargs)
  {
! 	int master_fd, slave_fd;
  #ifndef HAVE_OPENPTY
! 	char * slave_name;
  #endif
  #if defined(HAVE_DEV_PTMX) && !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY)
! 	PyOS_sighandler_t sig_saved;
  #ifdef sun
! 	extern char *ptsname();
  #endif
  #endif
  
  #ifdef HAVE_OPENPTY
! 	if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
! 		return posix_error();
  #elif defined(HAVE__GETPTY)
! 	slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
! 	if (slave_name == NULL)
! 		return posix_error();
  
! 	slave_fd = open(slave_name, O_RDWR);
! 	if (slave_fd < 0)
! 		return posix_error();
  #else
! 	master_fd = open(DEV_PTY_FILE, O_RDWR | O_NOCTTY); /* open master */
! 	if (master_fd < 0)
! 		return posix_error();
! 	sig_saved = signal(SIGCHLD, SIG_DFL);
! 	/* change permission of slave */
! 	if (grantpt(master_fd) < 0) {
! 		signal(SIGCHLD, sig_saved);
! 		return posix_error();
! 	}
! 	/* unlock slave */
! 	if (unlockpt(master_fd) < 0) {
! 		signal(SIGCHLD, sig_saved);
! 		return posix_error();
! 	}
! 	signal(SIGCHLD, sig_saved);
! 	slave_name = ptsname(master_fd); /* get name of slave */
! 	if (slave_name == NULL)
! 		return posix_error();
! 	slave_fd = open(slave_name, O_RDWR | O_NOCTTY); /* open slave */
! 	if (slave_fd < 0)
! 		return posix_error();
  #if !defined(__CYGWIN__) && !defined(HAVE_DEV_PTC)
! 	ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */
! 	ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm */
  #ifndef __hpux
! 	ioctl(slave_fd, I_PUSH, "ttcompat"); /* push ttcompat */
  #endif /* __hpux */
  #endif /* HAVE_CYGWIN */
  #endif /* HAVE_OPENPTY */
  
! 	return Py_BuildValue("(ii)", master_fd, slave_fd);
  
  }
  #endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX) */
  
! #ifdef HAVE_FORKPTY
  PyDoc_STRVAR(posix_forkpty__doc__,
  "forkpty() -> (pid, master_fd)\n\n\
  Fork a new process with a new pseudo-terminal as controlling tty.\n\n\
  Like fork(), return 0 as pid to child process, and PID of child to parent.\n\
  To both, return fd of newly opened pseudo-terminal.\n");
- 
  static PyObject *
  posix_forkpty(PyObject *self, PyObject *noargs)
  {
! 	int master_fd, pid;
  
! 	pid = forkpty(&master_fd, NULL, NULL, NULL);
! 	if (pid == -1)
! 		return posix_error();
! 	if (pid == 0)
! 		PyOS_AfterFork();
! 	return Py_BuildValue("(ii)", pid, master_fd);
  }
  #endif
  
--- 2597,2784 ----
  #endif /* defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) || defined(HAVE_DEV_PTMX */
  
  #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)
  static PyObject *
! __shared_openpty (int * out_master_fd, int * out_slave_fd)
  {
!     int master_fd, slave_fd;
  #ifndef HAVE_OPENPTY
!     char * slave_name;
  #endif
  #if defined(HAVE_DEV_PTMX) && !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY)
!     PyOS_sighandler_t sig_saved;
  #ifdef sun
!     extern char *ptsname();
  #endif
  #endif
  
  #ifdef HAVE_OPENPTY
!     if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
!         return posix_error();
  #elif defined(HAVE__GETPTY)
!     slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
!     if (slave_name == NULL)
!         return posix_error();
  
!     slave_fd = open(slave_name, O_RDWR);
!     if (slave_fd < 0)
!         return posix_error();
  #else
!     master_fd = open(DEV_PTY_FILE, O_RDWR | O_NOCTTY);
!     if (master_fd < 0){
!         return posix_error();
!     }
!     sig_saved = signal(SIGCHLD, SIG_DFL);
!     /* change permission of slave */
!     if (grantpt(master_fd) < 0) {
!         signal(SIGCHLD, sig_saved);
!         return posix_error();
!     }
!     /* unlock slave */
!     if (unlockpt(master_fd) < 0) {
!         signal(SIGCHLD, sig_saved);
!         return posix_error();
!     }
!     signal(SIGCHLD, sig_saved);
!     slave_name = ptsname(master_fd);
!     if (slave_name == NULL){
!         return posix_error();
!     }
!     slave_fd = open(slave_name, O_RDWR | O_NOCTTY);
!     if (slave_fd < 0){
!         return posix_error();
!     }
  #if !defined(__CYGWIN__) && !defined(HAVE_DEV_PTC)
!     ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */
!     ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm */
  #ifndef __hpux
!     ioctl(slave_fd, I_PUSH, "ttcompat"); /* push ttcompat */
  #endif /* __hpux */
  #endif /* HAVE_CYGWIN */
  #endif /* HAVE_OPENPTY */
  
!     *out_master_fd = master_fd;
!     *out_slave_fd = slave_fd;
!     return Py_BuildValue("(ii)", master_fd, slave_fd);
! }
  
+ PyDoc_STRVAR(posix_openpty__doc__,
+ "openpty() -> (master_fd, slave_fd)\n\n\
+ Open a pseudo-terminal, returning open fd's for both master and slave end.\n");
+ static PyObject *
+ posix_openpty(PyObject *self, PyObject *noargs)
+ {
+     int master_fd;
+     int slave_fd;
+ 
+     return __shared_openpty (& master_fd, & slave_fd);
  }
  #endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX) */
  
! /* Use forkpty if available. For platform that don't have it I try to define it. */
! #if defined(HAVE_FORKPTY) || (defined(HAVE_FORK) && (defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)))
  PyDoc_STRVAR(posix_forkpty__doc__,
  "forkpty() -> (pid, master_fd)\n\n\
  Fork a new process with a new pseudo-terminal as controlling tty.\n\n\
  Like fork(), return 0 as pid to child process, and PID of child to parent.\n\
  To both, return fd of newly opened pseudo-terminal.\n");
  static PyObject *
  posix_forkpty(PyObject *self, PyObject *noargs)
  {
! #ifdef HAVE_FORKPTY        /* The easy one */
!     int master_fd, pid;
!     pid = forkpty(&master_fd, NULL, NULL, NULL);
! #else                    /* The hard one */
!     int master_fd, pid;
!     int slave_fd;
!     char * slave_name;
!     int fd;
! 
!     __shared_openpty (& master_fd, & slave_fd);
!     if (master_fd < 0 || slave_fd < 0)
!     {
!         return posix_error();
!     }
!     slave_name = ptsname(master_fd);
  
!     pid = fork();
!     switch (pid) {
!     case -1:
!             return posix_error();
!     case 0: /* Child */
! 
! #ifdef TIOCNOTTY
!         /* Explicitly close the old controlling terminal.
!         Some platforms require an explicit detach of the current controlling tty
!         before we close stdin, stdout, stderr.
!         OpenBSD says that this is obsolete, but doesn't hurt. */
!         fd = open("/dev/tty", O_RDWR | O_NOCTTY);
!         if (fd >= 0) {
!             (void) ioctl(fd, TIOCNOTTY, (char *)0);
!             close(fd);
!         }
! #endif /* TIOCNOTTY */
! 
!         /* The setsid() system call will place the process into its own session
!             which has the effect of disassociating it from the controlling terminal.
!             This is known to be true for OpenBSD.
!          */
!         if (setsid() < 0){
!             return posix_error();
!         }
! 
! 
!         /* Verify that we are disconnected from the controlling tty. */
!         fd = open("/dev/tty", O_RDWR | O_NOCTTY);
!         if (fd >= 0) {
!             close(fd);
!             return posix_error();
!         }
! 
! #ifdef TIOCSCTTY
!         /* Make the pseudo terminal the controlling terminal for this process
!          (the process must not currently have a controlling terminal).
!         */
!         if (ioctl(slave_fd, TIOCSCTTY, (char *)0) < 0){
!             return posix_error();
!         }
! #endif /* TIOCSCTTY */
! 
!         /* Verify that we can open to the slave pty file. */
!         fd = open(slave_name, O_RDWR);
!         if (fd < 0){
!             return posix_error();
!         }
!         else
!             close(fd);
! 
!         /* Verify that we now have a controlling tty. */
!         fd = open("/dev/tty", O_WRONLY);
!         if (fd < 0){
!             return posix_error();
!         }
!         else {
!             close(fd);
!         }
! 
!         (void) close(master_fd);
!         (void) dup2(slave_fd, 0);
!         (void) dup2(slave_fd, 1);
!         (void) dup2(slave_fd, 2);
!         if (slave_fd > 2)
!             (void) close(slave_fd);
!         pid = 0;
!         break;
!     default:
!         /* PARENT */
!         (void) close(slave_fd);
!     }
! #endif
! 
!     if (pid == -1)
!         return posix_error();
!     if (pid == 0)
!         PyOS_AfterFork();
!     return Py_BuildValue("(ii)", pid, master_fd);
  }
  #endif
  
***************
*** 6994,7000 ****
  #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)
  	{"openpty",	posix_openpty, METH_NOARGS, posix_openpty__doc__},
  #endif /* HAVE_OPENPTY || HAVE__GETPTY || HAVE_DEV_PTMX */
! #ifdef HAVE_FORKPTY
  	{"forkpty",	posix_forkpty, METH_NOARGS, posix_forkpty__doc__},
  #endif /* HAVE_FORKPTY */
  #ifdef HAVE_GETEGID
--- 7093,7099 ----
  #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)
  	{"openpty",	posix_openpty, METH_NOARGS, posix_openpty__doc__},
  #endif /* HAVE_OPENPTY || HAVE__GETPTY || HAVE_DEV_PTMX */
! #if defined(HAVE_FORKPTY) || (defined(HAVE_FORK) && (defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)))
  	{"forkpty",	posix_forkpty, METH_NOARGS, posix_forkpty__doc__},
  #endif /* HAVE_FORKPTY */
  #ifdef HAVE_GETEGID

--------------020003030503000503080704--