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

I am writing a script to grab the output from snoop or tcpdump and decode a Cisco Discovery Protocol packet. I want to have snoop/tcpdump killed after so many seconds if I don't get a packet.

I have tried using alarm in an eval block per the example in the alarm doc and it works great. Only problem is that after the script ends I sometimes have zombie processes of tcpdump hanging around if I die in the eval block. Is there a way around this or is the best way to use fork? From the perlipc manpage is seems that the backticks should take care of any zombies without having to call wait(), but I guess not. TIA for the help!
eval { local $SIG{ALRM} = sub { die "tcpdump Died\n" }; alarm 20; @captured_data = `$tcpdump -i $nic -c1 -xx -s0 'ether[20:2] == + 0x2000' 2>/dev/null`; alarm 0; }; # Check the status of eval to see if we alarmed out if ($@) { return; } else { return @captured_data; }; }; After the script runs: grapeape:~# ps -ef |grep tcpdump |grep -v grep root 3499 1 1 16:26 pts/1 00:00:00 sh -c /usr/sbin/tcpdum +p -i eth0 -c1 -xx -s0 'ether[20:2] == 0x2000' 2>/dev/null root 3500 3499 2 16:26 pts/1 00:00:00 /usr/sbin/tcpdump -i e +th0 -c1 -xx -s0 ether[20:2] == 0x2000

Replies are listed 'Best First'.
Re: Eval leaving zombies when dying with alarm
by pc88mxer (Vicar) on Apr 25, 2008 at 21:07 UTC
    Just install a SIGCHLD handler:
    use POSIX ":sys_wait_h"; sub reaper { do { $kid = waitpid(-1, WNOHANG); } while $kid > 0; } $SIG{CHLD} = \&reaper;
    There might be another way to specify this kind of SIGCHLD action, but this is basically what needs to be done.

    Update: using $SIG{CHLD} = 'IGNORE'; should also do this.

      Thanks for the reply. Maybe I am not implementing it correctly because I still have the tcpdump process hanging around. I'll play around with it some more.
        Apparently those processes are not zombies. You'll need to kill the tcpdump process when the alarm goes off, and in order to do that you'll need to know its pid.

        Actually, I think I'd use a different approach:

        my $pid = open(T, "tcpdump ...|"); eval { while (1) { $SIG{ALRM} = sub { die "time to stop" } alarm(20); my $line = <T>; # read a line from tcpdump alarm(0); last unless defined($line); # process line } }; kill 9, $pid; close(T);
        Or, you can use IO::Select:
        my $pid = open my $T, "tcpdump ...|"; my $sel = new IO::Select($T); my $buf; while ($sel->can_read($T, 20)) { read($T, $buf, 1024, length($buf)) or last; } kill 9, $pid; close($T); # $buf contains output from tcpdump