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

I recently asked a question about some wierd code that forks in order to run a program.
Specifically, the program passes messages to the spamassassin spamc program for spam analysis, and (is supposed to) return the results.
I have re-written the code as:
#!/usr/bin/perl -w use strict; use Fcntl qw(:DEFAULT :flock); use vars qw (%spamcerr); %spamcerr= ( 64 => "command line usage error", 65 => "data format error", 66 => "cannot open input", 67 => "addressee unknown", 68 => "host name unknown", 69 => "service unavailable", 70 => "internal software error", 71 => "system error (e.g., can't fork)", 72 => "critical OS file missing", 73 => "can't create (user) output file", 74 => "input/output error", 75 => "temp failure; user is invited to retry", 76 => "remote error in protocol", 77 => "permission denied", 78 => "configuration error", ); my $pipecmd = '/usr/local/bin/spamc -c -x -t60 -u @@@USERNAME@@@'; my $msgfile = $ARGV[0] || die "no message defined\n"; open(MSG, "<$msgfile") || die "can't open message for reading\ +n"; my $message = join('',<MSG>); close(MSG) || die "can't close msgfile $msgfile\n" +; my $output = pipecmd_msg($pipecmd, \$message); print "The output returned is $output\n"; print "The status returned is $?\n"; sub pipecmd_msg { # refer to perlopentut and perlipc for more information. my ($pipecmd, $r_message)=@_; my $username=getpwuid($>); # username of euid $pipecmd=~s/\@\@\@USERNAME\@\@\@/$username/g; print "pipecmd: $pipecmd\n"; my ($outfh, $outfile)=mktmpfile('spamcheck.out'); my ($errfh, $errfile)=mktmpfile('spamcheck.err'); chmod 0666, $outfile, $errfile; local $|=1; # flush all output # alias STDERR and STDOUT to get the output of the pipe open(STDERR,">&=".fileno($errfh)) or return("dup STDERR failed: $!" +); open(STDOUT,">&=".fileno($outfh)) or return("dup STDOUT failed: $!" +); local $SIG{PIPE} = 'IGNORE'; # don't die if the fork pipe breaks my ($out, $err, $errmsg); open(P, "|$pipecmd") or return("can't fork to pipecmd: $! pipecmd: +$pipecmd"); if (ref($r_message) eq 'ARRAY') { print P @{$r_message} or return("can't write to pipe: $!"); } else { print P ${$r_message} or return("can't write to pipe: $!"); } # automatic reaping happening here? :( close(P); # don't warn because spamc may exit successfully on its o +wn, breaking the pipe # If a message is spam, spamc will exit with 1. # If a message is not spam, spamc will exit with 0. # If there is an error, spamc will exit with some other value. my $exit_status = $? >> 8; unless ($exit_status == 1 || $exit_status == 0) { return("pipe problem = check spamc and spamd are working = statu +s: $? exit_status: $exit_status"); } close($errfh) or return("can't close errfh: $!"); close($outfh) or return("can't close outfh: $!"); sysopen(F, $errfile, O_RDONLY) or return("can't open errfile $errfi +le: $!"); $err = <F>; close(F) or return("can't close errfile $errfile: $!"); # unlink $errfile; sysopen(F, $outfile, O_RDONLY) or return("can't open outfile $outfi +le: $!"); $out = <F>; close(F) or return("can't close errfile $outfile: $!"); # unlink $outfile; return $err if (defined $err && $err ne '' && $err !~ m/^\s+$/s); return $out; } sub mktmpfile { my $fh= do { local *FH }; for (1..5) { my $n=rand(); $n=~s/^0.0*//; $n=substr($n,0,8); my $fname=untaint("./tmp.$_[0].$$-$n"); return($fh, $fname) if (sysopen($fh, $fname, O_RDWR|O_CREAT|O_EX +CL)); } return; } sub untaint { local $_ = shift; # this line makes param into a new variable. d +on't remove it. local $1; # fix perl $1 taintness propagation bug m/^(.*)$/s; return $1; }

Now I'm really stumped. This code works fine on the command line as is. However, when I put the pipecmd code into my CGI the fork always exits before the close() on the P filehandle with a status of -1. Reading perlipc I see that this is the status when a process is automatically reaped. But why is it reaping automatically under CGI and not from the commandline? Is there a better way to fork to spamc and capture the STDOUT while feeding it STDIN?

Thanks!

Replies are listed 'Best First'.
Re: open fork returning -1 status...
by wazoox (Prior) on Nov 06, 2006 at 14:57 UTC
    I suppose the CGI runs with a special user, like "apache", "httpd" or "nobody". Try running your script from command line as the same user, someting like
    su apache -c myscript.pl
    You may get some useful information. I suppose this is some "access denied" problem, though.
Re: open fork returning -1 status...
by suaveant (Parson) on Nov 06, 2006 at 16:07 UTC
    This isn't actually an answer to your question, just a suggestion. Have you looked at IPC::Open3? Using that you wouldn't have to just through so many hoops to get the output of Spamassassin, not to mention avoiding temp files. Just a thought.

                    - Ant
                    - Some of my best work - (1 2 3)

Re: open fork returning -1 status...
by suaveant (Parson) on Nov 06, 2006 at 16:13 UTC
    I couldn't say for sure but it may help point you in the right direction. In my experience with Apache (1 at least) when you do things like close STDOUT I have seen it kill off the cgi. Maybe since you are playing with STDOUT the web server is getting antsy and attacking you :) In that case maybe my IPC::Open3 suggestion actually will fix the problem... just thought of that. Hope it helps.

                    - Ant
                    - Some of my best work - (1 2 3)