gnu@perl has asked for the wisdom of the Perl Monks concerning the following question:

I have a little program for pinging multiple hosts (below). It runs fine, but after looking at it for a while I think it is doing quite a bit of redundant work and wanted to get some other opinions.

I think that the " for (<IPS>) " and the " for (@hosts) " are being run in each child as well as the work that the child needs to do. I just wanted to make sure this is the case before I get into a rewrite.

I have not written very many forked programs and would like input on better methods as well.

Also, I am working on writing this program setuid root. I don't want to do that, but I want to be able to use the icmp ability in Net::Ping. I think it would help with performance to not have to fork the ping each time.

When I setuid root the script and set the owner to root I get 'Insecure dependency in open while running setuid at /dev/fd/3 line 17.'. This is the first open of a FH. What is this from?

TIA, Chad.

#!/usr/bin/perl -w use strict; use IO::Handle; use Net::Ping; my $source_file = (shift || "/tmp/ping_list.txt"); my $good_out = "/tmp/$ENV{LOGNAME}_good_ips.txt"; my $bad_out = "/tmp/$ENV{LOGNAME}_bad_ips.txt"; my %pid_to_host; my %host_result; my @hosts; open(GOOD,">$good_out") or die "Cannot open output file $good_out: $!\n"; open(BAD,">$bad_out") or die "Cannot open output file $bad_out: $!\n"; open(IPS,"$source_file") or die "Cannot open source file $source_file: $!\n"; autoflush GOOD 1; autoflush BAD 1; autoflush STDOUT 1; for (<IPS>) { chomp; s/ *//; s/\cM//; if ($_ =~ m/^\d+\.\d+\.\d+\.\d+$/) { push(@hosts,$_); } else { print BAD "$_ : NOT A VALID IP ADDRESS\n"; } } for (@hosts) { wait_for_a_kid() if keys %pid_to_host > 70; if ( my $pid = fork ) { # parent $pid_to_host{$pid} = $_; } else { # child exit !ping_a_host($_); } } 1 while wait_for_a_kid(); for (sort keys %host_result) { print GOOD "$_\n" if ($host_result{$_}); print BAD "$_\n" if (!$host_result{$_}); } sub ping_a_host { my $host = shift; #my $p = Net::Ping->new('icmp',5); #$p->ping($host) ? 1 : 0; `/usr/sbin/ping -n $host 1 2>/dev/null` =~ /no answer from/ ? 0 + : 1; } sub wait_for_a_kid { my $pid = wait; return 0 if $pid < 0; my $host = delete $pid_to_host{$pid} or warn("Why did I see $pid ($?)\n"), next; $host_result{$host} = $? ? 0 : 1; 1; }
[download]

Replies are listed 'Best First'.
Re: forking question
by Zaxo (Archbishop) on Feb 20, 2003 at 19:23 UTC
    I think that the " for (<IPS>) " and the " for (@hosts) " are being run in each child as well as the work that the child needs to do. I just wanted to make sure this is the case before I get into a rewrite.

    Don't rewrite on that account, your program is fine as far as that goes. The <IPS> loop is only run once, in the parent. The kids don't start up until that loop is done. The kids call exit when their job is done, so they don't go on to duplicate effort in the @hosts loop, either.

    I do recommend a couple of changes, though. At minimum, you should check for definedness of $pid. If it is undefined, you are in the parent and fork failed. Your code shunts the parent into the child branch to exit in that case.

    For a tidy and easy rewrite, look at Net::Ping and Parallel::ForkManager. The latter is very handy for limiting the number of concurrent forked processes, and it takes care of failures and child reaping fo you, too.

    After Compline,
    Zaxo

Re: forking question
by tall_man (Parson) on Feb 20, 2003 at 19:47 UTC
    Your 'Insecure dependency in open while running setuid' comes from taint checking, which is automatically turned on when you run suid, as if you had specified the '-T' flag.

    What it means is that all strings from outside sources are viewed with suspicion because they might contain security attacks. Your $good_out and $bad_out filenames contain strings with $ENV{LOGNAME}, and that is the source of the taint. See perlsec for more details. How do you untaint? Here is an example:

    my $logname = $ENV{LOGNAME}; $logname =~ m/(.*)/; $logname = quotemeta($logname);
    Then you can interpolate $logname for your file names.

      I think the following code would be more consice:

      my $logname = quotemeta($ENV{LOGNAME});

      In your version the regex does nothing since you don't use the results from it. quotemeta will automatically untaint the variable for you, so you don't need a regex at all. Perhaps it was a typo, and you meant to use quotemeta($1)? Not a big deal, but I thought it was worth mentioning.

      Your explaination for why there was a problem is spot on though...

Re: forking question
by gnu@perl (Pilgrim) on Feb 20, 2003 at 19:12 UTC
    I must apologize profusely for my premature post. After having looked all around on perlmonks and google, I have finally found my answer.

    I did not realize that the fork-ed process picked up from the point at which fork was called. I was led to believe (by another programmer) that it started the fork-ed process at the beginning.

    I would still appreciate comments and any input on the setuid problem I mentioned.

    Thanks, Chad.