failed exec

Matts on 2002-11-04T19:35:20

Does anyone know how to return errors to the parent process after you've fork()ed, when your exec() fails?

I'm doing this in the clamd tests:

my $pid = fork();
die "Fork failed" unless defined $pid;
if (!$pid) {
  exec($clamd);
}
...
But if clamd fails to start for some reason, I don't know how to find out reliably. exec() of course will return an error, but I don't know how to propogate that to the parent.


Exec failure

Ovid on 2002-11-04T20:08:22

What do you mean by "exec() fails"? From perldoc -f exec

It fails and returns false only if the command does not exist *and* it is executed directly instead of via your system's command shell (see below).

Under what conditions do you expect the call to fail? Unless the docs are wrong, if the program you are trying to execute exists, you will never get a false value from exec. Perhaps system() would be a better choice, and you can handle terminating the child process manually?

Re:Exec failure

Matts on 2002-11-04T20:48:53

Sorry I guess I wasn't clear. I mean it fails in exactly the manner described that you quoted ;-)

i.e. how can I communicate back to the parent this error condition?

Re:Exec failure

rjray on 2002-11-04T21:12:47

Non-zero exit code from the child. Your wait call's return value can be used to make sure that you get the expected child's death. You can use a time-out, since "success" probably means that the child never effectively exits (or if it does, the time-out is probably not needed). When wait returns the PID of the deceased child, $? will have the status/exit code.

Would this work?

Ovid on 2002-11-04T22:40:38

Maybe you can use some variant of the following? We use Windows boxes at work (damn it!), so I rarely get a chance to do much nifty stuff with forking. As a result, I could totally be smoking crack with this.

#!/usr/bin/perl -w
use strict;

#!/usr/bin/perl -w
use strict;
use IO::Handle;
pipe( READER, WRITER );
WRITER->autoflush(1);

# Change the following line to a non-existent program to test
my $program = '/usr/bin/echo';
my $pid = fork;
die unless defined $pid;

unless ($pid) {
    close READER;
    local $^W;
    {exec {$program} 'something'};
    print WRITER "Child ($$) failed to exec ($program)\n";
    close WRITER;
    exit;
}
else {
    close WRITER;
    my $line = <READER>;
    if ( $line ) {
        chomp $line;
        print "Parent ($$) read ($line)\n";
    }
    else {
        print "Nothing to report, sir!\n";
    }
    close READER;
}

Re:Would this work?

Matts on 2002-11-05T08:44:34

I don't think so, because the line:
my $line = <READER>;
would block, so if the exec() worked fine, it would never return. I'll try it though - you never know.

Plus if you set the pipe to non-blocking reads, then there's timing issues. How long do you wait for something to come out of the pipe?

exit

jmason on 2002-11-04T23:05:24

wotcher Matt --

just exit(1), and make sure the parent calls wait() at some point (or polls with waitpid(WNOHANG)), and checks the exit status.

How IPC::Run does it

barries on 2002-11-05T14:32:07

IPC::Run does it by opening a pipe() to sync to the child, fork()ing, and marking it as close on exit with a fcntl(). Here are some snippets glued together from pieces of IPC::Run:
   ( $sync_reader_fd, $self->{SYNC_WRITER_FD} ) = _pipe ;
   $kid->{PID} = fork() ;
   croak "$! during fork" unless defined $kid->{PID} ;

   unless ( $kid->{PID} ) {
      eval {   
         open $s1, ">&=$self->{SYNC_WRITER_FD}"
            or croak "$! setting filehandle to fd SYNC_WRITER_FD" ;
         fcntl $s1, F_SETFD, 1 ;
         die "exec failed: simulating exec() failure"
            if $self->{_simulate_exec_failure} ;

         _exec $kid->{PATH}, @{$kid->{VAL}}[1..$#{$kid->{VAL}}] ;

         croak "exec failed: $!" ;
      } ;
      if ( $@ ) {
         _write $self->{SYNC_WRITER_FD}, $@ ;
      ## Avoid DESTROY.
         POSIX::exit 1  ;
      }
   }

   _debug "fork() = ", $kid->{PID} if _debugging_details ;

   ## Wait for kid to get to it's exec() and see if it fails.
   _close $self->{SYNC_WRITER_FD} ;
   my $sync_pulse = _read $sync_reader_fd ;
   _close $sync_reader_fd ;

   if ( ! defined $sync_pulse || length $sync_pulse ) {
      if ( waitpid( $kid->{PID}, 0 ) >= 0 ) {
         $kid->{RESULT} = $? ;
      }
      else {
         $kid->{RESULT} = -1 ;
      }
      $sync_pulse =
         "error reading synchronization pipe for $kid->{NUM}, pid $kid->{PID}"
         unless length $sync_pulse ;
      croak $sync_pulse ;
   }
   return $kid->{PID} ;

- Barrie

Re:How IPC::Run does it

barries on 2002-11-05T14:39:05

make that "close on exec()", not "close on exit".

- Barrie