简体   繁体   中英

Perl, how do I create a pipe to my exec'd child?

I am trying to pass data from my perl script to my c program using a pipe (uni-directional). I need to find a way to to do this without messing with the child programs STDIN or STDOUT, so I try creating a new handle and passing the fd.

I create 2 IO::Handles and create a pipe. I write to one end of the pipe and attempt to pass the File descriptor of the other end of the pipe to my child program that is being execed. I pass the file descriptor by setting an ENV variable. Why does this not work? (It does not print out 'hello world'). As far as I know, file descriptors and pipes are inherited by the child when exec'd.

Perl script:

#!/opt/local/bin/perl
use IO::Pipe;
use IO::Handle;

my $reader = IO::Handle->new();
my $writer = IO::Handle->new();
$reader->autoflush(1);
$writer->autoflush(1);
my $pipe = IO::Pipe->new($reader, $writer);
print $writer "hello world";
my $fh = $reader->fileno;
$ENV{'MY_FD'} = $fh;
exec('./child') or print "error opening app\n";
# No more code after this since exec replaces the current process

C Program, app.c (Compiled with gcc app.c -o child ):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char ** argv) {
  int fd = atoi(getenv("MY_FD"));
  char buf[12];
  read(fd, buf, 11);
  buf[11] = '\0';
  printf("fd: %d\n", fd);
  printf("message: %s\n", buf);
}

Output:

fd: 3
message:

The message is never passed through the pipe to the C program. Any suggestions?

Your pipe file descriptors are set FD_CLOEXEC, and so are closed upon exec().

Perl's $^F controls this behavior. Try something like this, before you call IO::Pipe->new:

$^F = 10;  # Assumes we don't already have a zillion FDs open

Alternatively, you can with Fcntl clear the FD_CLOEXEC flag yourself after creating the pipe.

I found the solution. Some people said that it was not possible with exec, that it would not see pipes or file descriptors, but that was not correct.

Turns out that perl closes/invalidates all fd > 2 automatically unless you say otherwise.

Adding the following flags to the FD fixes this problem (where READ is the handle here, NOT STDIN):

my $flags = fcntl(READ, F_GETFD, 0);
fcntl(READ, F_SETFD, $flags & ~FD_CLOEXEC);

Your program is failing because exec calls another program and never returns . It isn't designed for communication with another process at all.

You probably wrote the above code based on the IO::Pipe documentation , which says "ARGS are passed to exec". That isn't what it means, though. IO::Pipe is for communication between two processes within your Perl script, which are created by fork . They mean the execution of the new process, rather than a call to exec in your own code.

Edit: for one-directional communication, all you need is open with a pipe :

open my $prog, '|-', './child' or die "can't run program: $!";

print {$prog} "Hello, world!";

Rodrigo, I can tell you that your file descriptor is no longer valid when you exec into the c app.
Please be aware that I just say it is INVALID, but it still exists in the environment variables. The FD=3 will continue existing until the whole process ends.
You can check the fd by fcntl. The code is listing below

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char ** argv) {
  int fd = atoi(getenv("MY_FD"));
  char buf[12];
  read(fd, buf, 11);
  buf[11] = '\0';
  printf("fd: %d, if fd still valid: %d\n", fd, fcntl(fd, F_GETFD));
  printf("strlen %d\n", (int)strlen(buf));
  printf("message: %s\n", buf);
}

You can see that MY_FD=3 will always in ENV as the process doesn't destroy itself, so you can get fd as 3. But, this file descriptor has been invalid. so the result of fcntl(fd, F_GETFD) will be -1, and the length you read from fd will be 0.
That's why you will never see the "hello world" sentence.

One more thing, @dan1111 is right, but you don't need to open a new pipe, as you have already done so.
All you need to is just set MY_FD=0, like

$ENV{'MY_FD'} = 0;

The STDIN/OUT is another independent process that always exists, so the pipe will not broken down when your perl app exec into c app. That's why you can read from what you input in app.
If your requirement is writing from another file hanle, please try to make that file handle an independent process and always exist, just like STDIN.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM