re-launch piped external program

R

rihad

Hello, Perl hackers,

I'm writing a Perl script that runs FreeBSD's ipfw(8) and writes
certain commands to it through a pipe, line by line:

open OUT, "|-", "ipfw", "/dev/stdin" or die "$! $?";
while (1) {
print OUT $command, "\n";
}

Sometimes $command causes parse errors (due to manipulation of a
nonexistent rule, or similar), and ipfw dies... Is there any way to
catch and acknowledge that fact, and re-run myprog for subsequent
iterations?
 
B

Ben Morrow

Quoth rihad said:
Hello, Perl hackers,

I'm writing a Perl script that runs FreeBSD's ipfw(8) and writes
certain commands to it through a pipe, line by line:

open OUT, "|-", "ipfw", "/dev/stdin" or die "$! $?";

Don't use global filehandles. Use variable instead, and give them proper
names:

open my $IPFW, "|-", "ipfw", "/dev/stdin" or...
while (1) {
print OUT $command, "\n";

You can avoid the need for this "\n" by setting $\.
}

Sometimes $command causes parse errors (due to manipulation of a
nonexistent rule, or similar), and ipfw dies... Is there any way to
catch and acknowledge that fact, and re-run myprog for subsequent
iterations?

If you attempt to write to a pipe which has been closed (say, because
the process on the other end has died) your program will be sent a
SIGPIPE. The default action for this is to terminate your program, but
you can catch it (see perldoc perlipc) and recover. Alternatively, you
can ignore it ($SIG{PIPE} = 'IGNORE') and check the return value from
print. If it failed, and $! == EPIPE (EPIPE can be imported from the
POSIX module), then your process has died. This may be simpler than
trying to use a signal handler (signals don't really behave terribly
well).

Note that you should explicitly close the filehandle if the process
dies, as that causes perl to wait for the child; if you don't, you'll
start accumulating zombie processes.

Ben
 
X

xhoster

rihad said:
Hello, Perl hackers,

I'm writing a Perl script that runs FreeBSD's ipfw(8) and writes
certain commands to it through a pipe, line by line:

open OUT, "|-", "ipfw", "/dev/stdin" or die "$! $?";
while (1) {
print OUT $command, "\n";
}

Sometimes $command causes parse errors (due to manipulation of a
nonexistent rule, or similar), and ipfw dies... Is there any way to
catch and acknowledge that fact, and re-run myprog for subsequent
iterations?

If ipfw dies, then your process should be delivered a CHLD signal,
which can be captured by putting code in $SIG{CHLD};

my $foo;
while (1) {
$foo=1;
$SIG{CHLD}=sub {$foo=0};
open OUT, "|-", "ipfw", "/dev/stdin" or die "$! $?";
while ($foo) {
print OUT $command, "\n";
}
close OUT or warn "failed with $? $!";
};

There is no general reason to think that the program will die instantly
or that the signal will be delivered instantly, so you may not fall out of
the loop until several iterations after the $command that actually
triggered the error. So you are playing with fire. (especially since you
don't show us how $command ever changes, so there may be hidden problems
there as well).

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 
R

rihad

Thanks to you both, guys. I'll try the EPIPE trick as soon as I get
over this small problem:

$\ = "\n";
$| = 1;
open my $FW, "|-", "sudo", "ipfw", "/dev/stdin" or die "can't run
ipfw: $!";
$| = 1;
print $FW 'add';
sleep 10;
exit;

I gave ipfw erroneous input on purpose ('add' requires argument list).
No matter what I tried: ipfw's pre-mortem line on stderr "Line 1:
missing action" won't show up on the screen until after 10 seconds
have passed. And I can see ipfw hanging there during that time from
another console using ps. Looks like Perl holds on to the line for
some reason. Someone care to explain what I did wrong?
 
X

xhoster

rihad said:
Thanks to you both, guys. I'll try the EPIPE trick as soon as I get
over this small problem:

$\ = "\n";
$| = 1;
open my $FW, "|-", "sudo", "ipfw", "/dev/stdin" or die "can't run
ipfw: $!";
$| = 1;
print $FW 'add';
sleep 10;
exit;

I gave ipfw erroneous input on purpose ('add' requires argument list).
No matter what I tried: ipfw's pre-mortem line on stderr "Line 1:
missing action" won't show up on the screen until after 10 seconds
have passed. And I can see ipfw hanging there during that time from
another console using ps. Looks like Perl holds on to the line for
some reason. Someone care to explain what I did wrong?

Maybe ipfw doesn't flush stderr until either the buffer is full or until
its stdin gets closed, which doesn't happen here until the parent program
exits.

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 
R

rihad

have passed. And I can see ipfw hanging there during that time from
Maybe ipfw doesn't flush stderr until either the buffer is full or until
its stdin gets closed, which doesn't happen here until the parent program
exits.

*Perl* holds on to the $command line even though I turned auto-flush
on with $| = 1;
As I said ipfw is hanging there all along, which wouldn't be the case
if it got the flawed command from the pipe immediately.
 
P

Peter Scott

Thanks to you both, guys. I'll try the EPIPE trick as soon as I get
over this small problem:

$\ = "\n";
$| = 1;
open my $FW, "|-", "sudo", "ipfw", "/dev/stdin" or die "can't run
ipfw: $!";
$| = 1;
print $FW 'add';
sleep 10;
exit;

perldoc perlvar:

$| If set to nonzero, forces a flush right away and after every
write or print on the currently selected output channel.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You did not select $FW as the current output channel.
 
M

Martien verbruggen

perldoc perlvar:

$| If set to nonzero, forces a flush right away and after every
write or print on the currently selected output channel.

You did not select $FW as the current output channel.

This probably is a good spot to draw attention to IO::Handle's autoflush
method. rather than selecting the file handle you want to flush, you set
the autoflush attribute on those filehandles you want to flush. I find
that generally fits better with the way I think it should work.

Regards,
Martien
 
R

rihad

You did not select $FW as the current output channel.
Thank you! Worked like a charm. On a sidenote: I'm a bit new to Perl
(surprise!), and I'm at chapter 4 of "Learning Perl" ("The Llama
book") right now. Then I will read Programming Perl ("The Camel book")
and tons of perldocs. I think perldocs are best at giving minute
technical details, as you mentioned. Thanks again!
 
R

rihad

How can I *easily* run an external program, writing commands to it on
a filehandle, and reading its replies from another? Ironically,
reading Perl FAQ [*] left one question unanswered: where's chat2.pl so
much talked about? I'm asking someone to give a modern (v5.8.8)
example.

[*] http://www.faqs.org/faqs/perl-faq/part5/
 
X

xhoster

rihad said:
How can I *easily* run an external program, writing commands to it on
a filehandle, and reading its replies from another?

That is kind of like asking "How can I easily solve the 3 body problem
of classical gravitational mechanics". You can't, in general. In this
case, that is because it depends on the details of how the external command
does its buffering, and whether it produces anything on stderr, and other
things. If all ducks line up in a row properly, then it might be easy, but
unless you know what "properly" means, which itself is not easy, then you
won't know if it is easy or not.

Ironically,
reading Perl FAQ [*] left one question unanswered: where's chat2.pl so
much talked about?
http://search.cpan.org/src/ANDYD/perl5.003_07/lib/chat2.pl

I'm asking someone to give a modern (v5.8.8)
example.

Since the v5.8.8 doesn't talk about chat2, you shouldn't expect
to find a 5.8.8 example of chat2.pl.

That is very very old FAQ.

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 
R

rihad

How can I *easily* run an external program, writing commands to it on
a filehandle, and reading its replies from another? Ironically,
reading Perl FAQ [*] left one question unanswered: where's chat2.pl so
much talked about? I'm asking someone to give a modern (v5.8.8)
example.

Use the IPC::Run module.

The aforementioned FAQ claims such open2 usage might cause deadlocks.
But the FAQ's old (written in 1996). Is it still risky to use open2
under FreeBSD?
 
R

rihad

The aforementioned FAQ claims such open2 usage might cause deadlocks.
But the FAQ's old (written in 1996). Is it still risky to use open2
under FreeBSD?

Answering to myself: yes, it is :) I'm able to set things up with
use IPC::Open2;
my ($ipfw_in, $ipfw_out);
my $ipfw_pid = open2($ipfw_in, $ipfw_out, 'sudo', 'ipfw', '/dev/
stdin');
print $ipfw_out 'add pipe 3 ip from 5.6.7.8 to any out' . "\n";
print <$ipfw_in>;


The last line hangs and deadlocks! Looks like ipfw doesn't flush its
output. Luckily its sources are right here ready to be hacked.
 
M

Mumia W.

How can I *easily* run an external program, writing commands to it on
a filehandle, and reading its replies from another? Ironically,
reading Perl FAQ [*] left one question unanswered: where's chat2.pl so
much talked about? I'm asking someone to give a modern (v5.8.8)
example.
[*]http://www.faqs.org/faqs/perl-faq/part5/
Use the IPC::Run module.

The aforementioned FAQ claims such open2 usage might cause deadlocks.
But the FAQ's old (written in 1996). Is it still risky to use open2
under FreeBSD?

Look at the IPC::Run documentation.

http://search.cpan.org/search?query=IPC+Run&mode=module

Open2 can cause deadlocks depending upon what you're trying to do;
IPC::Run has ways to get around that problem. Never read that FAQ file
again; this is a much better Perl FAQ list.

http://perldoc.perl.org/perlfaq.html
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top