popen bugs

Eric Blake ebb9@byu.net
Tue Aug 18 14:36:00 GMT 2009


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

According to Christopher Faylor on 8/17/2009 5:02 PM:
> On Mon, Aug 17, 2009 at 03:45:23PM -0700, Eric Blake wrote:
>> popen misbehaves when various std fds are closed.
> 
> And, who among us could have not seen that coming?

I see you fixed the fd access from within child processes in CVS; thanks.
 However, there is still a bug: POSIX requires that children of subsequent
popen's no longer see the fd of the first popen, without regards to
FD_CLOEXEC.  Testing on FreeBSD, Solaris, and Linux show that these OS's
leave the parent's fd as non-cloexec, but still obey this requirement.
So, they must do it by maintaining a list of fd's opened by popen, and
explicitly close everything in that list when calling popen again.  And at
least the newlib implementation (which probably stems from the BSD
implementation) already has to maintain this list for making pclose work
correctly.

Cygwin is not compatible with Linux in this regard, because it is relying
on marking the fd as cloexec, and an explicit call to remove the cloexec
flag will leak the fd of the first child into the second popen contrary to
POSIX.  (And this is probably partly my fault, for implementing it wrong
in newlib, for which I will be submitting a patch today.)

There's also several extensions to consider: a recent addition to glibc
allows:

popen (cmd, "re");

to mark the parent's fd as cloexec (and even do so atomically once pipe2
is supported), and if cygwin ever gets bi-directional pipes, at least
FreeBSD supports:

popen (cmd, "r+");

to set the parent FILE as read/write and the child gets the pipe for both
stdin and stdout.

$ cat foo.c # program to sniff whether popen sets cloexec flag
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
  FILE *c = popen (":", "r");
  printf ("%x %x\n", FD_CLOEXEC, fcntl (fileno (c), F_GETFD));
  return 0;
}

$ cat bar.c # program to show that cygwin violates posix
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main (int argc, char **argv)
{
  switch (argc)
    {
    case 1:
      /* driver - create two children */
      {
	FILE *c1, *c2;
	char cmd1[80], cmd2[80];
	snprintf (cmd1, 80, "%s r", argv[0]);
	c1 = popen (cmd1, "r");
	fcntl (fileno (c1), F_SETFD,
	       fcntl (fileno (c1), F_GETFD) & ~FD_CLOEXEC);
	snprintf (cmd2, 80, "%s %d r", argv[0], fileno (c1));
	c2 = popen (cmd2, "r");
	fprintf (stderr, "read %c from child2\n", getc (c2));
	fprintf (stderr, "read %c from child1\n", getc (c1));
	pclose (c1);
	pclose (c2);
      }
      break;
    case 2:
      /* first child, argv[1] is zero to read, else write */
      if (atoi (argv[1]))
	fprintf (stderr, "child1 read %c\n", getchar ());
      else
	fprintf (stderr, "child1 putchar returned %c\n", putchar ('1'));
      break;
    case 3:
      /* second child, argv[1] is fd that should be closed (if not 0
	 or 1), argv[2] is zero to read, else write */
      if (dup2 (atoi (argv[1]), 6) == 6)
	fputs ("child2 saw leaked fd\n", stderr);
      if (atoi (argv[2]))
	fprintf (stderr, "child2 read %c\n", getchar ());
      else
	fprintf (stderr, "child2 putchar returned %c\n", putchar ('2'));
      break;
    default:
      return 1;
    }
  return 0;
}


- --
Don't work too hard, make some time for fun as well!

Eric Blake             ebb9@byu.net
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkqKvFQACgkQ84KuGfSFAYCp1QCdHwhXOr8a7rm/lvL1JlmjZ+fH
BFwAoJk434DGHTdyUQktA6HS2LB7jxp/
=KHZi
-----END PGP SIGNATURE-----

--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple



More information about the Cygwin mailing list