Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

No inherited file handles with tie() and exec()

by saintmike (Vicar)
on Apr 15, 2009 at 01:23 UTC ( #757524=perlquestion: print w/replies, xml ) Need Help??

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

Child processes inherit their parent's file handles. So, if you want to redirect STDOUT to a file in both the parent and the child, you can do something like this:
close STDOUT; open STDOUT, ">out" or die; my $pid = fork(); die "" if !defined $pid; exit 0 if $pid; # parent exits print "Message!\n"; #child prints to file
This works as expected and prints to the file, not to STDOUT. You can do something similar by tie()ing the file handle to a trapper class:
tie *STDOUT, TrapClass; my $pid = fork(); die "" if !defined $pid; exit 0 if $pid; # parent exits print "Message!\n"; # child prints to file package TrapClass; sub TIEHANDLE { my $class = shift; open(my $fh, ">out") or die; bless { fh => $fh }, $class; } sub PRINT { my $self = shift; my $fh = $self->{fh}; print $fh @_; }
This works as well, printing to the file instead of STDOUT. However, if you replace the print statement by exec() to overload the child process with something else, like /bin/date, it doesn't work anymore, the child process will print to STDOUT, not to the file:
tie *STDOUT, TrapClass; my $pid = fork(); die "" if !defined $pid; exit 0 if $pid; # parent exits exec "/bin/date"; # child prints to STDOUT!! package TrapClass; sub TIEHANDLE { my $class = shift; open(my $fh, ">out") or die; bless { fh => $fh }, $class; } sub PRINT { my $self = shift; my $fh = $self->{fh}; print $fh @_; }
This is puzzling, given that exec() works just fine if you redirect the file handle the good-old fashioned way:
close STDOUT; open STDOUT, ">out" or die; my $pid = fork(); die "" if !defined $pid; exit 0 if $pid; # parent exits exec "/bin/date"; #child prints to file
Does anyone know what kind of dark magic is going on in tie() to breaks the third case?

Replies are listed 'Best First'.
Re: No inherited file handles with tie() and exec()
by ikegami (Pope) on Apr 15, 2009 at 02:19 UTC
    A tied handle is a Perl variable that Perl presents as a file handle, but isn't. When a program passes a tied handle to a Perl IO function, Perl calls a subroutine instead of doing IO calls. There's no way for Perl to cause a child process (that might not even be a Perl program) to call a sub in its parent instead of writing to a handle. As such, children cannot inherit tied handles or any other handle without a fileno.
      Yeah, that makes sense. And I guess the reason why case #2 works with tie() and a forked process is that in this case the child knows about the tie()ed subroutine as well. Thanks!

        I had a problem confirming your statement until I realize it's very unclear what you mean by a process "knowing a sub".

        The child gets a copy of the tied var, the sub and everything else in the process's memory space. It only works because 1) the child uses the copy of the tied var and 2) the child process doesn't expect to cause any changes in the parent.

Re: No inherited file handles with tie() and exec()
by almut (Canon) on Apr 15, 2009 at 02:22 UTC

    The exec'ed process /bin/date knows nothing about Perl file handles. It always prints to file descriptor 1 (stdout).  In your last case where you're redirecting the good-old fashioned way, the file descriptor associated with Perl's STDOUT is still 1 after the redirection. OTOH, the descriptor associated with the file handle $fh (opened within TIEHANDLE) isn't 1. (Try fileno to verify this yourself.)

    Update: the descriptor's "close-on-exec" flag (FD_CLOEXEC) also plays a role here.  By default it is on (i.e. do close) for everything but descriptors 0-2. But you could clear the flag, so the descriptor associated with $fh would survive the exec. In this case, you could then redirect /bin/date's output to that inherited file descriptor (typically number 3 or 4):

    #!/usr/bin/perl tie *STDOUT, TrapClass; my $pid = fork(); die "" if !defined $pid; exit 0 if $pid; # parent exits my $tied_descr = fileno(STDOUT); exec "/bin/date >&$tied_descr"; # child package TrapClass; use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC); sub TIEHANDLE { my $class = shift; open(my $fh, ">out") or die; my $flags = fcntl($fh, F_GETFD, 0) or die "Can't get flags: $!\n"; # clear close-on-exec flag fcntl($fh, F_SETFD, $flags & ~FD_CLOEXEC) or die "Can't set flags: + $!\n"; bless { fh => $fh }, $class; } sub PRINT { my $self = shift; my $fh = $self->{fh}; print $fh @_; } sub FILENO { my $self = shift; my $fh = $self->{fh}; return fileno($fh); }

    As desired, the output of /bin/date would end up in the file 'out' that you've tied STDOUT to. However, this is not happening via Perl's tied subroutine calls, but rather via the child directly writing to the associated file descriptor...

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (7)
As of 2020-05-30 06:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    If programming languages were movie genres, Perl would be:















    Results (171 votes). Check out past polls.

    Notices?