-----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 e...@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