Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Passing a File Descriptor to a New Process on Windows

by hardburn (Abbot)
on Apr 23, 2014 at 13:28 UTC ( [id://1083353]=perlquestion: print w/replies, xml ) Need Help??

hardburn has asked for the wisdom of the Perl Monks concerning the following question:

I have a script that opens a filehandle and then calls fork() and exec() to run a separate program for doing the reading. The trouble is that by default, Perl sets the close-on-exec flags on new filehandles. This can be changed with Fcntl:

use Fcntl; my $flags = fcntl $in, F_GETFD, 0 or die "fcntl F_GETFD: $!"; fcntl $in, F_SETFD, $flags & ~FD_CLOEXEC or die "fcntl F_SETFD: $!";

This doesn't work on Strawberry Perl on Windows, though, due to the F_GETFD macro being missing.

At Sebastian Riedel's suggestion, I tried local $^F = 10; instead. This makes Perl consider up to 10 open filehandles to be "system filehandles" which do not get the close-on-exec flag set (when using this, remember that you'll already have three filehandles open: STDIN, STDOUT, and STDERR).

This worked on Linux and Cygwin, but Strawberry Perl still failed to reopen the filedescriptor with "Bad file descriptor". This is the error you usually get when the close-on-exec flag is still set.

Strawberry Perl is built with threads:

This is perl 5, version 18, subversion 1 (v5.18.1) built for MSWin32-x +64-multi-thread

Since fork() would be emulated with threads in this case, perhaps that accounts for the difference with Cygwin?

Full example code below. The first one (fd_pass.pl) is passed a file to open and then forks off to exec the second one (fd_get.pl).

fd_pass.pl

#!/usr/bin/perl use v5.14; use warnings; #use Fcntl; my $FILE = shift or die "Need file to read\n"; # Don't set close-on-exec flag when we open a file handle local $^F = 10; say "Opening file"; open( my $in, '<', $FILE ) or die "Can't open '$FILE': $!\n"; # Clear the close-on-exec flag #my $flags = fcntl $in, F_GETFD, 0 or die "fcntl F_GETFD: $!"; #fcntl $in, F_SETFD, $flags & ~FD_CLOEXEC or die "fcntl F_SETFD: $!"; $SIG{CHLD} = 'IGNORE'; my $child_pid = fork(); if( $child_pid ) { # Parent while(1) { sleep 10 } } else { # Child say "Forking child process"; my $fd = fileno( $in ); exec( 'perl', './fd_get.pl', $fd ) or die "Could not exec: $!\n"; }

fd_get.pl

#!/usr/bin/perl use v5.14; use warnings; my $FD = shift or die "Need file descriptor\n"; open( my $in, '<&', $FD ) or die "Could not open file descriptor '$FD' +: $!\n"; while(<$in>) { chomp; say "Got in child: $_"; } close $in;

"There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Replies are listed 'Best First'.
Re: Passing a File Descriptor to a New Process on Windows
by Corion (Patriarch) on Apr 23, 2014 at 13:32 UTC

    There is IO::FDPass, which implements the appropriate Windows system calls.

      For the sake of clarity, IO::FDPass doesn't implement letting a child process inherit an open file descriptor. It implements passing an open file descriptor through a Unix-domain socket (and similar functionality on Windows).

      It also doesn't work with cygwin Perl.

      To pass an open file descriptor to a child process in Windows Perl you actually have to pass an open low-level Windows file handle to the child and you have to create the child with something other than fork.

      The core module Win32API::File includes the required functionality for looking up the low-level handle so you can inform the child where to find it and for opening a Perl file handle so you can read from and/or write to it.

      There are several ways to spawn the child process, including: Win32::Process::Create(), system(1,...) (see perlport), system("start ...").

      Unfortunately, although I've done this successfully before and have helped others do it at PerlMonks, none of us has actually posted working examples of this as far as I can tell.

      But it wasn't particularly hard. You do the steps the same as on Unix except:

      • You set the "INHERIT" flag via Win32API::File::SetHandleInformation().
      • You get the low-level handle via FdGetOsFHandle().
      • You don't use fork() (see above).
      • You "open" the handle via OsFHandleOpen().

      - tye        

        Thanks, tye, have this all figured out now.

        fd_pass.pl

        #!/usr/bin/perl use v5.14; use warnings; use Win32API::File 'FdGetOsFHandle'; my $FILE = shift or die "Need file to read\n"; say "Opening file"; open( my $in, '<', $FILE ) or die "Can't open '$FILE': $!\n"; # Get the real FD from Windows my $fd = FdGetOsFHandle( fileno($in) ); say "Spawning child process"; my $pid = system(1, 'perl', './fd_get.pl', $fd ) or die "Could not spawn child process: $!\n"; wait;

        fd_get.pl

        #!/usr/bin/perl use v5.14; use warnings; use Win32API::File 'OsFHandleOpen'; my $FD = shift or die "Need file descriptor\n"; OsFHandleOpen( *IN, $FD, 'r' ) or die "Could not open file descriptor '$FD': $!\n"; while(<IN>) { chomp; say "Got in child: $_"; } close IN;

        "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Re: Passing a File Descriptor to a New Process on Windows
by eyepopslikeamosquito (Archbishop) on Apr 23, 2014 at 20:46 UTC

    I remember writing a test t/run/cloexec.t a few years back. The comment block from this test may be of interest:

    # Test inheriting file descriptors across exec (close-on-exec). # # perlvar describes $^F aka $SYSTEM_FD_MAX as follows: # # The maximum system file descriptor, ordinarily 2. System file # descriptors are passed to exec()ed processes, while higher file # descriptors are not. Also, during an open(), system file descripto +rs # are preserved even if the open() fails. (Ordinary file descriptors # are closed before the open() is attempted.) The close-on-exec # status of a file descriptor will be decided according to the value +of # C<$^F> when the corresponding file, pipe, or socket was opened, not # the time of the exec(). # # This documented close-on-exec behaviour is typically implemented in # various places (e.g. pp_sys.c) with code something like: # # #if defined(HAS_FCNTL) && defined(F_SETFD) # fcntl(fd, F_SETFD, fd > PL_maxsysfd); /* ensure close-on-exec +*/ # #endif # # This behaviour, therefore, is only currently implemented for platfor +ms # where: # # a) HAS_FCNTL and F_SETFD are both defined # b) Integer fds are native OS handles # # ... which is typically just the Unix-like platforms. # # Notice that though integer fds are supported by the C runtime librar +y # on Windows, they are not native OS handles, and so are not inherited # across an exec (though native Windows file handles are).

    See also:

    I'm afraid it's too long ago, and I don't remember why I was so interested close-on-exec back then, but as tye points out above, it is possible to pass native Windows file handles.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1083353]
Approved by rnewsham
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (3)
As of 2024-04-19 20:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found