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

Greetings ... I am writing a daemon utility which forks off a child, that may execute fairly arbitrary code (defined by a series of external files which get eval'd in), which given the nature of the daemon to initate build requests, will most likely include many calls to system(), thus creating dubious sub-children, whose output I'd like to catch using the same pipe, ideally ... but the problem is that their output doesn't get caught by my pipe call.

So ... the execute subroutine goes something like so:



# ... in another init subroutine, the external files get eval'd in,
# defining code references and data structures, in preparation
# for this task_exec subroutine ...

sub task_exec {
   # ... perform pre-init ... 

   if($pid = fork){
       # ... parent: save $pid in a hash record ...
   } else {

       # first child, set up pipes:

       pipe(CHILD_RDR, PARENT_WTR);
       CHILD_RDR->autoflush(1);
       PARENT_WTR->autoflush(1);

       if($fpid = fork()){
           select(PARENT_WTR);
           open(STDERR, ">&PARENT_WTR"); # merge STDOUT with STDERR

           # execute a list of code references, defined in a global structure:
           foreach $step (@{$TasksByName{$p{task}{steps}}}){
               chdir($p{sandbox});

               # execute the actual step code, some sequences might
               # go something like system("./configure") or "make"

               if(&$step( ... pass some stuff ... ) != 0){
                    print STDERR "something bad happened!\n";
                    exit(1);
               }
           }

           kill 'INT' => $fpid;  # sent interrupt to child reader
           waitpid($fpid, 0);

           exit(0);
       } else {
           # child reader, essentially acts like the "tee" program
           
           $fdout = new IO::File("> $p{sandbox}/task.out");
           if(!$fdout){
               die "can't open output file: $!";
           }

           $SIG{INT} = sub {
                      print STDERR "caught SIGINT, exiting!\n";
                      close(CHILD_RDR);
                      $fdout->close();
                      exit(0);
           };

           while(defined($l=<CHILD_RDR>)){
                print $fdout $l;
           }
           exit(0);
       }
       exit(0); # first child
   }
}

So, what's happening is that the contents of &$step(...) subroutines in turn call various commands via system(...), the output of which I would like to catch in my pipe call, which is currently not happening. The contents of &$step is defined in a series of external files that get eval'd in at init time, which also define pretty sophisticated structured data to be communicated to the server, which is why I chose this eval-centric approach in the first place (as opposed to doing something like system("task-script.pl > task.out").

What does your wisdom dictate that I should do? (And hopefully I'm explaining my predicament clearly enough.)

Replies are listed 'Best First'.
Re: Collecting STDOUT from childeren (and subchilderen) via pipe()
by pg (Canon) on Jan 11, 2004 at 01:30 UTC

    To be frank, the question is a little bit vague, at least to me. Hopefully I guessed your requirement correctly, and my two samples cover some of your needs.

    Two tips:

    • pipe() has to be before fork(), so that the pipe is known to both parent and child.
    • Close the end of the pipe that the process doesn't need.

    The first sample shows how to make one parent talk to multiple children thru the same pipe:

    use IO::Handle; use IO::Select; use strict; use warnings; $| ++; pipe(PARENT_RDR, CHILD_WTR); if (fork()) { if (fork()) { close CHILD_WTR; while (my $line = <PARENT_RDR>) { print "parent got $line"; } } else { close PARENT_RDR; for (100..110) { print CHILD_WTR "$_\n"; } } } else { close PARENT_RDR; for (0..10) { print CHILD_WTR "$_\n"; } }

    This prints:

    parent got 0 parent got 1 parent got 2 parent got 3 parent got 4 parent got 5 parent got 6 parent got 7 parent got 8 parent got 9 parent got 10 parent got 100 parent got 101 parent got 102 parent got 103 parent got 104 parent got 105 parent got 106 parent got 107 parent got 108 parent got 109 parent got 110

    The second sample shows how to make pipes chained, so that grandparent can talk to grandchild:

    use IO::Handle; use strict; use warnings; $| ++; pipe(GRANDPARENT_RDR, PARENT_WTR); if (fork()) { close PARENT_WTR; while (my $line = <GRANDPARENT_RDR>) { print "grand parent got $line"; } } else { close GRANDPARENT_RDR; pipe(PARENT_RDR, CHILD_WTR); if (fork()) { close CHILD_WTR; while (my $line = <PARENT_RDR>) { print "parent got $line"; print PARENT_WTR $line; } } else { close PARENT_RDR; for (0..10) { print CHILD_WTR "$_\n"; } } }

    This prints:

    parent got 0 parent got 1 parent got 2 parent got 3 parent got 4 parent got 5 parent got 6 parent got 7 parent got 8 parent got 9 parent got 10 grand parent got 0 grand parent got 1 grand parent got 2 grand parent got 3 grand parent got 4 grand parent got 5 grand parent got 6 grand parent got 7 grand parent got 8 grand parent got 9 grand parent got 10
Re: Collecting STDOUT from childeren (and subchilderen) via pipe()
by Zaxo (Archbishop) on Jan 11, 2004 at 04:39 UTC

    Your pipe needs to be set up before fork. In your code, the parent never sees either end.

    After Compline,
    Zaxo