Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Open a file on a specific file descriptor?

by benizi (Hermit)
on Jan 01, 2008 at 23:02 UTC ( #659891=perlquestion: print w/replies, xml ) Need Help??

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

Lots of djb programs and related/similar utilities (e.g. sslclient) have interfaces that want data written to or read from specific file descriptors. (With sslclient: "sslclient runs prog, with file descriptors 6 and 7 reading from and writing to a child process").

Maybe I'm interpreting things incorrectly, but is there a way to open a file on a specific fd in Perl? I tried the following with open, but it seems to fail if the fd's not already open:

$ perl -lwe 'open STDOUT, ">&=6" or die ">&=6: $!"' >&=6: Bad file descriptor at -e line 1. $ perl -lwe 'open STDOUT, ">&=6" or die ">&=6: $!"' 6>/dev/null

The goal is to provide a similar interface, like:

# Don't use this - DOESN'T WORK open my $to_child, ">&=6" or die ">&=6: $!"; open my $from_child, "<&=7" or die "<&=7: $!"; die "fork:$!" if not defined(my $pid = fork); if ($pid) { print $to_child "data for child\n"; my $ret = <$from_child>; } else { exec { "sub-program" } "sub-program", @args; }

Replies are listed 'Best First'.
Re: Open a file on a specific file descriptor?
by dave_the_m (Monsignor) on Jan 01, 2008 at 23:24 UTC
    Perl's open() interface (and for that matter the underlying UNIX/Linux open(2) interface) don't allow you to specify a particular file descriptor. The usual way to handle this would be keep creating handles until the descriptor reaches the desired number. Note also that the parent should be creating a couple of pipes using the pipe() function to create the connections between parent and child.

    Dave.

      Perl's open() interface (and for that matter the underlying UNIX/Linux open(2) interface) don't allow you to specify a particular file descriptor.

      Ah, my mistake. I was confused by the fact that C's fdopen(3) is on the same man-page as fopen(3). "fdopen" is mentioned in perldoc -f open when discussing open's '>&=6' syntax. I saw fopen(path, mode) and fdopen(fd, mode) and conflated the two's capabilities. (open a FILE* given a path, and open a FILE* given an fd number).

      The usual way to handle this would be keep creating handles until the descriptor reaches the desired number.

      Hmm. Looking at the source for sslclient, it seems the strategy is:

      1. Close fd's 6 and 7.
      2. Open a pipe.
      3. fd_move the pipe's ends to 6 and 7.

      fd_move is a qmail-ism, apparently. It uses fcntl(current-fd,F_DUPFD,desired-fd) behind the scenes.

      Note also that the parent should be creating a couple of pipes using the pipe() function to create the connections between parent and child.

      Oops, yes. Thanks. Forgot in my for-this-node example that parent's "in" is child's "out" and vice versa.

      For the curious, perldoc perlipc also suggests that my case warrants $SIG{PIPE} = 'IGNORE'; and $SIG{CHLD} = sub { }; # do something to handle a dead child

Re: Open a file on a specific file descriptor?
by polettix (Vicar) on Jan 02, 2008 at 01:32 UTC
    I don't understand if you're trying to use those programs or to recreate similar ones.

    In the former case, your Perl program already has the child, so file descriptors 6 and 7 will be open when your program is started, and the two opens in your example should more or less work. In this case, you can also take a look at IO::Handle to get handles for file descriptors you already have.

    OTOH, if you want to recreate the same "effect", you should probably first ask yourself if it really makes sense (e.g. maybe redirecting child's STDIN and STDOUT just before calling exec can be acceptable), then use dave_the_m's suggestion.

    Update: added stuff about IO::Handle

    Hey! Up to Dec 16, 2007 I was named frodo72, take note of the change! Flavio
    perl -ple'$_=reverse' <<<ti.xittelop@oivalf

    Io ho capito... ma tu che hai detto?
      I don't understand if you're trying to use those programs or to recreate similar ones.

      The attempt was a little of both, but I should have specified that I was trying to provide an sshclient-like interface for a qmail filtering program, since (as you mentioned) running a Perl program as sshclient's child is pretty easy.

      I'd used sshclient in the past, and even though I often mix up which fd is read and which is write does what (different djb/djb-like util's use different fd's for different things), I like the nice, generic, cross-language interface. It fits nicely with both "Do one thing, and do it well" and "Everything's a pipe". I wondered how easy it'd be to provide that interface in Perl.

      The idea was that I would provide the filtering program (child) fd's 0,1,4,5,6,7 for message-in, message-out, header-in, header-out, body-in, and body-out. If any of the '-out's was the same as its '-in' or was empty, the original would be used. And, either message-out or (header-out + body-out) would be used, depending on what the filter program changed.

      Since this isn't easy to do, I'm probably going to use an option flag to specify which one of 'message', 'headers', or 'body' the filter wants to have and just use STDIN + STDOUT.

      Thanks

      Update: clarified my confusion re: djb{|-esque} tools' use of fd's

        Well, opening the fd you like isn't difficult at all, as dave_the_m suggested - even if I have to admit that things might be less portable than I'm impling here. Pragmatically, what I see in my Linux system is that the lowest available file descriptor is always going to be used.

        The first thing you have to pay attention to is the starting file descriptor. For a brand new process, you should get 0, 1 and 2 assigned to standard input, standard output and standard error resp. (I know you *already* know this, but bear with me). So, if you really want to start from 4 instead of 3 (I wonder why, but I'm sure you have a good reason), you first have to do a fake open to get the spare file descriptor:

        open my $fh, '<', '/dev/zero' or die "$!";
        Then, pipe calls come in pairs, so you can get the couples you want:
        pipe my ($header_in, $header_out); pipe my ($body_in, $body_out);
        To take into account your need for handles 0 and 1 as well:
        use Fatal qw( close ); close STDIN; close STDOUT; pipe my ($message_in, $message_out);

        The best thing to do is probably write a subroutine for this:

        #!/usr/bin/perl use strict; use warnings; use Fatal qw( close ); # monitored (auto-)close close STDIN; close STDOUT; my %handle_for; @handle_for{qw( message_in message_out )} = pipe_from(0); @handle_for{qw( header_in header_out )} = pipe_from(4); @handle_for{qw( body_in body_out )} = pipe_from(6); print {*STDERR} "$_ has descriptor ", fileno($handle_for{$_}), "\n" for sort { fileno($handle_for{$a}) <=> fileno($handle_for{$b}) } keys %handle_for; sub pipe_from { # Assuming consecutive numbers are ok my ($n) = @_; my @fakes; while (1) { open my $fh, '<', '/dev/zero' or die "open(): $!"; push @fakes, $fh; last if fileno($fh) == $n; die "file descriptor $n not available" if fileno($fh) > $n; } ## end while (1) close pop @fakes; # Free last used descriptor, that's equal to $n pipe my ($r, $w); die "pipe(): $!" unless defined $r; close $_ for @fakes; return ($r, $w); } ## end sub pipe_from __END__ poletti@PolettiX:~/sviluppo/perl$ perl pipes.pl message_in has descriptor 0 message_out has descriptor 1 header_in has descriptor 4 header_out has descriptor 5 body_in has descriptor 6 body_out has descriptor 7
        I know, using this for each pair means that you will re-open fake handles for lower descriptors at each call. Feel free to optimise, if you think this is really needed :)

        Update: if you're wondering why I'm explicitly close-ing the handles instead of letting scoping do its work, I have to admit that I've been biten by this. In particular, I noted that the file descriptor 0 wasn't released when getting out of the while. If anyone can shed a light on it I'd be grateful! This was the original loop:

        while (1) { open my $fh, '<', '/dev/zero' or die "open(): $!"; last if fileno($fh) == $n; die "file descriptor $n not available" if fileno($fh) > $n; push @fakes, $fh; } ## end while (1)
        and of course I had no close pop @fakes; line after it. I thought that it could be that any time you open file descriptor 0, you can access it via STDIN (that is *STDIN{IO}), so when $fh goes out of scope the handle's reference count does not drop to zero, and auto-closing is not triggered.

        Hey! Up to Dec 16, 2007 I was named frodo72, take note of the change! Flavio
        perl -ple'$_=reverse' <<<ti.xittelop@oivalf

        Io ho capito... ma tu che hai detto?
Re: Open a file on a specific file descriptor?
by Anonymous Monk on Jan 02, 2008 at 14:53 UTC
    Looping until you reach the fd is wasteful. Use POSIX::dup2.
Re: Open a file on a specific file descriptor?
by nikosv (Deacon) on Jan 05, 2008 at 21:51 UTC
    open(FILE, "> &=".fileno (HANDLE));

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2022-08-17 00:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?