fork, exec, and signal handling

T

Todd Pytel

Hi all,

An infrequent perl coder here, having some issues with signal handling.
I'm not understanding something about how forking works with signals, I
think. The script is supposed to loop, running tcpdump until it exits
(after a certain number of packets are captured), rotating the dump output
and analyzing the output with tcpstat. Since the tcpdump can be rather
lengthy, I'd like to catch TERM signals and have the output analyzed so as
not to lose data. Here's some of what I've come up with - the use of pid
files is rather ghetto, I think, so suggestions are welcome:

$SIG{'TERM'} = \&term_handler;

sub main {
while ( 1 ) {
( $stop ) && exit;
defined (my $pid = fork) or die "Cannot fork: $!";
unless ($pid) {
exec "tcpdump -c $packet_count -i $interface -w $output > /dev/null 2>&1";
}
open PIDFILE, "> $dumppid";
print PIDFILE "$pid\n";
close PIDFILE;
waitpid($pid, 0);
unlink $dumppid;
&rotate;
&analyze; # Should fork this, too - don't want to wait on analysis
}
}

sub term_handler {
print "Running term_handler...\n";
$stop = 1;
if ( -e $dumppid ) {
open PIDFILE, "$dumppid";
while (<PIDFILE>) {
chomp;
kill 15, $_;
}
close PIDFILE;
}
}

The problem is that the signal never gets caught, so the tcpdump keeps
going, and the cleanup at the end of main never occurs. From what I've
seen searching, this has something to do with the parent waiting on the
child process, but I'm not experienced enough to see exactly what the
problem is. Is there a solution or alternate approach that I can use here?

Thanks,
Todd Pytel
 
A

Anno Siegel

Todd Pytel said:
Hi all,

An infrequent perl coder here, having some issues with signal handling.
I'm not understanding something about how forking works with signals, I
think. The script is supposed to loop, running tcpdump until it exits
(after a certain number of packets are captured), rotating the dump output
and analyzing the output with tcpstat. Since the tcpdump can be rather
lengthy, I'd like to catch TERM signals and have the output analyzed so as
not to lose data. Here's some of what I've come up with - the use of pid
files is rather ghetto, I think, so suggestions are welcome:

Why do you think you need to store the pid in a file? Just declare
the $pid variable outside the loop and use it in term_handler().
$SIG{'TERM'} = \&term_handler;

sub main {
while ( 1 ) {
( $stop ) && exit;

Why the parentheses around $stop? Also, $stop is undeclared.
defined (my $pid = fork) or die "Cannot fork: $!";
unless ($pid) {
exec "tcpdump -c $packet_count -i $interface -w $output > /dev/null 2>&1";
}
open PIDFILE, "> $dumppid";
print PIDFILE "$pid\n";
close PIDFILE;
waitpid($pid, 0);
unlink $dumppid;
&rotate;
&analyze; # Should fork this, too - don't want to wait on analysis

Don't call subs with a leading "&" unless you want the effects associated
with that form.

[more code snipped]

Try something along these lines (untested):

$SIG{'TERM'} = \&term_handler;

main();

my $stop = 0;
my $pid;
sub main {
while ( 1 ) {
$stop && do { print "main exiting\n"; exit };
defined ($pid = fork) or die "Cannot fork: $!";
unless ($pid) {
exec "tcpdump -c $packet_count -i $interface " .
"-w $output > /dev/null 2>&1";
}
waitpid($pid, 0);
&rotate;
&analyze; # Should fork this, too - don't
# want to wait on analysis
}
}

sub term_handler {
print "Running term_handler...\n";
$stop = 1;
return unless $pid;
kill 15, $pid;
}

Anno
 
B

Brian McCauley

Todd Pytel said:
An infrequent perl coder here, having some issues with signal handling.
I'm not understanding something about how forking works with signals, I
think.

No, you are not understanding what exec() does.

exec() completely replaces the current process with a new executable.

If you set up a signal handler then exec() the signal handler no
longer exists in the new process.

This has nothing to do with Perl.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
A

Anno Siegel

Brian McCauley said:
No, you are not understanding what exec() does.

exec() completely replaces the current process with a new executable.

If you set up a signal handler then exec() the signal handler no
longer exists in the new process.

Well, the OP didn't explain the problem very well, but I think exec is fine
where it is. The Idea was, if I understand, to make a TERM signal to the
parent kill the child process (instead of the parent).

To the OP: Perl's system() already does something very similar -- to INTR
and TERM, as I recall, but look it up.

Anno
 
C

Charles DeRykus

...
lengthy, I'd like to catch TERM signals and have the output analyzed so as
not to lose data. Here's some of what I've come up with - the use of pid
files is rather ghetto, I think, so suggestions are welcome:

$SIG{'TERM'} = \&term_handler;

sub main {
while ( 1 ) {
( $stop ) && exit;
defined (my $pid = fork) or die "Cannot fork: $!";
unless ($pid) {
exec "tcpdump -c $packet_count -i $interface -w $output > /dev/null 2>&1";
}
open PIDFILE, "> $dumppid";
print PIDFILE "$pid\n";
close PIDFILE;
waitpid($pid, 0);
unlink $dumppid;
&rotate;
&analyze; # Should fork this, too - don't want to wait on analysis
}
}

sub term_handler {
print "Running term_handler...\n";
$stop = 1;
if ( -e $dumppid ) {
open PIDFILE, "$dumppid";
while (<PIDFILE>) {
chomp;
kill 15, $_;
}
close PIDFILE;
}
}

The problem is that the signal never gets caught, so the tcpdump keeps
going, and the cleanup at the end of main never occurs. From what I've
seen searching, this has something to do with the parent waiting on the
child process, but I'm not experienced enough to see exactly what the
problem is. Is there a solution or alternate approach that I can use here?

I'm not sure what your search revealed but I don't see how
the parent's blocking waitpid could be problematic at all.
The TERM handler below for instance gets caught:

my $pid = fork;
die $! unless defined $pid;

$SIG{ TERM } = sub { die "caught a term ..."; };
unless ($pid) {
exec "sleep 600" or die "exec failed: $!"; }
else {
waitpid( $pid,0);
}

The only long shots I can think of is that the signal's
being blocked or ignored somewhere else. You might want
to trace what's happening in the rest of the code.

hth,
 
T

Todd Pytel

Thanks for the responses.

Yes, Brian, I'm aware that exec() replaces the current process. I'm
somewhat clueless, but not that clueless. As Charles and Anno suggested,
the problem was elsewhere. Two places, really...

1) The $SIG{'TERM'} line was defined globally, but *after* the program had
entered the main loop. Thus the signal wasn't being caught. I haven't done
signal handling before, and thought that perl "read ahead" of the loop.
Guess not.

2) The exec command included the shell redirection - "> /dev/null 2>&1".
This means the fork's pid is the pid of /bin/sh invoked to run tcpdump,
not the pid of tcpdump itself. Removing the redirection takes care of
that, which is fine since I was planning to daemonize the process anyway.

Thanks for the pushes in the right direction, and sorry if I was unclear.
All I've read is the llama book, and this is the first substantial script
I've done from scratch, as opposed to modifying other people's stuff.

--Todd
 

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,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top