Cisco CSS rancid script...
Wedge Martin
wedge at shopping.com
Sat Jan 3 18:27:43 UTC 2004
I call this 'cssrancid' and use the vendor type of 'css' to
differentiate it.
It's a total hack on the standard rancid script, but it has all the
functional differences, including one that was super hard to track
down...
The user profile, when the term length is changed to 65535 ( css handles
term len 0 stupidly and gives you a term len of 24 ) it prompts you when
you log out to commit or discard user profile changes, screwing up the
session by hanging indefinitely... so the trick is to copy the profile
to user-profile; i found this _buried_ in cisco documentation.. goofy
voodoo.. but it works. i threw in a couple of other little hacks to
make this functional, and it's working nicely..
feel free to put this in the next distribution... i'll clean it up a
bit too and send you any changes i make.
#!/bin/perl
##
##
## Copyright (C) 1997-2001 by Henry Kilmer.
## All rights reserved.
##
## This software may be freely copied, modified and redistributed
without
## fee for non-commerical purposes provided that this copyright notice
is
## preserved intact on all copies and modified copies.
##
## There is no warranty or other guarantee of fitness of this software.
## It is provided solely "as is". The author(s) disclaim(s) all
## responsibility and liability with respect to this software's usage
## or its effect upon hardware, computer systems, other software, or
## anything else.
##
##
#
# RANCID - Really Awesome New Cisco confIg Differ
#
# usage: rancid [-d] [-l] [-f filename | $host]
#
use Getopt::Std;
getopts('dflm');
$log = $opt_l;
$debug = $opt_d;
$debug = "true";
$file = $opt_f;
$host = $ARGV[0];
$clean_run = 0;
$found_end = 0;
$timeo = 90; # clogin timeout in seconds
my(%filter_pwds); # password filtering mode
# This routine is used to print out the router configuration
sub ProcessHistory {
my($new_hist_tag,$new_command,$command_string, at string)=(@_);
if((($new_hist_tag ne $hist_tag) || ($new_command ne $command))
&& defined %history) {
print eval "$command \%history";
undef %history;
}
if (($new_hist_tag) && ($new_command) && ($command_string)) {
if ($history{$command_string}) {
$history{$command_string} =
"$history{$command_string}@string";
} else {
$history{$command_string} = "@string";
}
} elsif (($new_hist_tag) && ($new_command)) {
$history{++$#history} = "@string";
} else {
print "@string";
}
$hist_tag = $new_hist_tag;
$command = $new_command;
1;
}
sub numerically { $a <=> $b; }
# This is a sort routine that will sort numerically on the
# keys of a hash as if it were a normal array.
sub keynsort {
local(%lines)=@_;
local($i) = 0;
local(@sorted_lines);
foreach $key (sort numerically keys(%lines)) {
$sorted_lines[$i] = $lines{$key};
$i++;
}
@sorted_lines;
}
# This is a sort routine that will sort on the
# keys of a hash as if it were a normal array.
sub keysort {
local(%lines)=@_;
local($i) = 0;
local(@sorted_lines);
foreach $key (sort keys(%lines)) {
$sorted_lines[$i] = $lines{$key};
$i++;
}
@sorted_lines;
}
# This is a sort routine that will sort on the
# values of a hash as if it were a normal array.
sub valsort{
local(%lines)=@_;
local($i) = 0;
local(@sorted_lines);
foreach $key (sort values %lines) {
$sorted_lines[$i] = $key;
$i++;
}
@sorted_lines;
}
# This is a numerical sort routine (ascending).
sub numsort {
local(%lines)=@_;
local($i) = 0;
local(@sorted_lines);
foreach $num (sort {$a <=> $b} keys %lines) {
$sorted_lines[$i] = $lines{$num};
$i++;
}
@sorted_lines;
}
# This is a sort routine that will sort on the
# ip address when the ip address is anywhere in
# the strings.
sub ipsort {
local(%lines)=@_;
local($i) = 0;
local(@sorted_lines);
foreach $addr (sort sortbyipaddr keys %lines) {
$sorted_lines[$i] = $lines{$addr};
$i++;
}
@sorted_lines;
}
# These two routines will sort based upon IP addresses
sub ipaddrval {
my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#);
$a[3]+256*($a[2]+256*($a[1]+256*$a[0]));
}
sub sortbyipaddr {
&ipaddrval($a) <=> &ipaddrval($b);
}
# This routine parses "show version"
sub ShowVersion {
print STDERR " In ShowVersion: $_" if ($debug);
while (<INPUT>) {
tr/\015//d;
study;
last if(/^$prompt/);
next if(/^(\s*|\s*$cmd\s*)$/);
return(-1) if (/command authorization failed/i);
if (/^Slave in slot (\d+) is running/) {
$slave = " Slave:";
next;
}
/^Cisco Secure PIX /i &&
ProcessHistory("COMMENTS","keysort","F1", "!Image: $_") &&
next;
/^IOS .* Software \(([A-Za-z-0-9]*)\), .*Version\s+(.*)$/ &&
ProcessHistory("COMMENTS","keysort","F1",
"!Image:$slave Software: $1, $2\n") && next;
/^([A-Za-z-0-9_]*) Synced to mainline version: (.*)$/ &&
ProcessHistory("COMMENTS","keysort","F2",
"!Image:$slave $1 Synced to mainline version: $2\n") &&
next;
/^Compiled (.*)$/ &&
ProcessHistory("COMMENTS","keysort","F3",
"!Image:$slave Compiled: $1\n") && next;
/^ROM: (System )?Bootstrap.*(Version.*)$/ &&
ProcessHistory("COMMENTS","keysort","G1",
"!ROM Bootstrap: $2\n") && next;
if (/^Hardware:\s+(.*), (.* RAM), CPU (.*)$/) {
ProcessHistory("COMMENTS","keysort","A1",
"!Chassis type: $1 - a PIX\n");
ProcessHistory("COMMENTS","keysort","A2",
"!CPU: $3\n");
ProcessHistory("COMMENTS","keysort","B1", "!Memory: $2\n");
}
/^Serial Number:\s+(.*)$/ &&
ProcessHistory("COMMENTS","keysort","C1", "!$_") && next;
/^Activation Key:\s+(.*)$/ &&
ProcessHistory("COMMENTS","keysort","C2", "!$_") && next;
/^ROM: \d+ Bootstrap .*(Version.*)$/ &&
ProcessHistory("COMMENTS","keysort","G2",
"!ROM Image: Bootstrap $1\n!\n") && next;
/^ROM: .*(Version.*)$/ &&
ProcessHistory("COMMENTS","keysort","G3","!ROM Image: $1\n")
&& next;
/^BOOTFLASH: .*(Version.*)$/ &&
ProcessHistory("COMMENTS","keysort","G4","!BOOTFLASH: $1\n")
&& next;
/^BOOTLDR: .*(Version.*)$/ &&
ProcessHistory("COMMENTS","keysort","G4","!BOOTLDR: $1\n")
&& next;
/^System image file is "([^\"]*)", booted via (\S*)/ &&
ProcessHistory("COMMENTS","keysort","F4","!Image: booted
$1\n") &&
next;
/^System image file is "([^\"]*)"$/ &&
ProcessHistory("COMMENTS","keysort","F5","!Image: $1\n") &&
next;
if (/(\S+)\s+\((\S+)\)\s+processor.*with (\S+[kK]) bytes/) {
my($proc) = $1;
my($cpu) = $2;
my($mem) = $3;
my($device) = "router";
$type = "CSS";
print STDERR "TYPE = $type\n" if ($debug);
ProcessHistory("COMMENTS","keysort","A1",
"!Chassis type:$slave $proc - a $type $device\n");
ProcessHistory("COMMENTS","keysort","B1",
"!Memory:$slave main $mem\n");
ProcessHistory("COMMENTS","keysort","A3","!CPU:$slave
$cpu\n");
next;
}
if (/(\S+) Silicon\s*Switch Processor/) {
if (!defined($C0)) {
$C0=1; ProcessHistory("COMMENTS","keysort","C0","!\n");
}
ProcessHistory("COMMENTS","keysort","C2","!SSP: $1\n");
$ssp = 1;
$sspmem = $1;
next;
}
/^(\d+[kK]) bytes of multibus/ &&
ProcessHistory("COMMENTS","keysort","B2",
"!Memory: multibus $1\n") && next;
/^(\d+[kK]) bytes of non-volatile/ &&
ProcessHistory("COMMENTS","keysort","B3",
"!Memory: nvram $1\n") && next;
/^(\d+[kK]) bytes of flash memory/ &&
ProcessHistory("COMMENTS","keysort","B5","!Memory: flash
$1\n") &&
next;
/^(\d+[kK]) bytes of .*flash partition/ &&
ProcessHistory("COMMENTS","keysort","B6",
"!Memory: flash partition $1\n") && next;
/^(\d+[kK]) bytes of Flash internal/ &&
ProcessHistory("COMMENTS","keysort","B4",
"!Memory: bootflash $1\n") && next;
if(/^(\d+[kK]) bytes of (Flash|ATA)?.*PCMCIA .*(slot|disk)
?(\d)/i) {
ProcessHistory("COMMENTS","keysort","B7",
"!Memory: pcmcia $2 $3$4 $1\n");
next;
}
if(/^WARNING/) {
if (!defined($I0)) {
$I0=1;
ProcessHistory("COMMENTS","keysort","I0","!\n");
}
ProcessHistory("COMMENTS","keysort","I1","! $_");
# The line after the WARNING is what to do about it.
$_ = <INPUT>; tr/\015//d;
ProcessHistory("COMMENTS","keysort","I1","! $_");
}
if (/^Configuration register is (.*)$/) {
$config_register=$1;
next;
}
}
return(0);
}
# Dummy routine to set term length....
sub TermLength {
# Dummy subroutine.. need to set term length differently for CSS
# boxes as term length 0 doesnt work correctly. POS.
return(0);
}
# Dummy routine to copy profile...
sub CopyProfile {
## Because the term length gets changed twice, the stupid
## box will ask you to save or discard changes. This prompt
## of couse breaks the interaction... strangely enough tho
## in a failover environment, only the secondary behaves this
## way.. the primary lets you log out and does not complain.
return(0);
}
# This routine parses "show boot"
sub ShowBoot {
# Pick up boot variables if 7000/7200/7500/12000/2900/3500;
# otherwise pick up bootflash.
print STDERR " In ShowBoot: $_" if ($debug);
while (<INPUT>) {
tr/\015//d;
last if (/^$prompt/);
next if (/^(\s*|\s*$cmd\s*)$/);
return(1) if /^\s*\^\s*$/;
return(-1) if (/command authorization failed/i);
return(1) if /Ambiguous command/i;
# return(1) if /(Invalid input detected|Type help or )/;
return(1) if /(Open device \S+ failed|Error opening \S+:)/;
next if /CONFGEN variable/;
if (!defined($H0)) {
$H0=1; ProcessHistory("COMMENTS","keysort","H0","!\n");
}
if ($type !~ /^(12[04]|7)/) {
if ($type !~ /^(29|35)00/) {
ProcessHistory("COMMENTS","keysort","H2","!BootFlash:
$_");
} else {
ProcessHistory("COMMENTS","keysort","H1","!Variable:
$_");
}
} elsif (/variable/) {
ProcessHistory("COMMENTS","keysort","H1","!Variable: $_");
}
}
ProcessHistory("COMMENTS","","","!\n");
return(0);
}
# This routine processes a "show run"
sub ShowRun {
print STDERR " In ShowRun: $_" if ($debug);
my($lineauto) = 0;
while (<INPUT>) {
tr/\015//d;
study;
last if(/^$prompt/);
return(-1) if (/command authorization failed/i);
# the pager can not be disabled per-session on the PIX
s/^<-+ More -+>\s*//;
/Non-Volatile memory is in use/ && return(-1); # NvRAM is
locked
$lineauto = 0 if (/^[^ ]/);
# skip the crap
if (/^(##+$|(Building|Current) configuration)/i) {
while (<INPUT>) {
next if (/^Current configuration\s*:/i);
next if (/^:/);
next if (/^([%!].*|\s*)$/);
next if (/^ip add.*ipv4:/); # band-aid for 3620
12.0S
last;
}
if (defined($config_register)) {
ProcessHistory("","","","!\nconfig-register
$config_register\n");
}
tr/\015//d;
}
# some versions have other crap mixed in with the bits in the
# block above
/^! (Last configuration|NVRAM config last)/ && next;
## CSS specific....
/Generated on/ && next;
# Dog gone Cool matches to process the rest of the config
/^tftp-server flash / && next; # kill any tftp remains
/^ntp clock-period / && next; # kill ntp clock-period
/^ length / && next; # kill length on serial lines
/^ width / && next; # kill width on serial lines
$lineauto = 1 if /^ modem auto/;
/^ speed / && $lineauto && next; # kill speed on serial lines
/^ clockrate / && next; # kill clockrate on serial
interfaces
if (/^(enable )?(password|passwd) / && $filter_pwds >= 1) {
ProcessHistory("ENABLE","","","!$1$2 <removed>\n");
next;
}
if (/^(enable secret) / && $filter_pwds >= 2) {
ProcessHistory("ENABLE","","","!$1 <removed>\n");
next;
}
if (/^username (\S+)(\s.*)? secret /) {
if ($filter_pwds >= 2) {
ProcessHistory("USER","keysort","$1","!username $1$2
secret <removed>\n");
} else {
ProcessHistory("USER","keysort","$1","$_");
}
next;
}
if (/^username (\S+)(\s.*)? password ((\d) \S+|\S+)/) {
if ($filter_pwds == 2) {
ProcessHistory("USER","keysort","$1","!username $1$2
password <removed>\n");
} elsif ($filter_pwds == 1 && $4 ne "5"){
ProcessHistory("USER","keysort","$1","!username $1$2
password <removed>\n");
} else {
ProcessHistory("USER","keysort","$1","$_");
}
next;
}
if (/^(\s*)password / && $filter_pwds >= 1) {
ProcessHistory("LINE-PASS","","","!$1password <removed>\n");
next;
}
if (/^\s*neighbor (\S*) password / && $filter_pwds >= 1) {
ProcessHistory("","","","! neighbor $1 password
<removed>\n");
next;
}
if (/^(ppp .* password) 7 .*/ && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
if (/^(ip ftp password) / && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
if (/^( ip ospf authentication-key) / && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
# isis passwords appear to be completely plain-text
if (/^\s+isis password (\S+)( .*)?/ && $filter_pwds >= 1) {
ProcessHistory("","","","!isis password <removed>$2\n");
next;
}
if (/^\s+(domain-password|area-password) (\S+)( .*)?/
&& $filter_pwds
>= 1) {
ProcessHistory("","","","!$1 <removed>$2\n"); next;
}
# this is reversable, despite 'md5' in the cmd
if (/^( ip ospf message-digest-key \d+ md5) / && $filter_pwds >=
1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
if (/^((crypto )?isakmp key) \S+ / && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed> $'"); next;
}
# i am told these are plain-text on the PIX
if (/^(vpdn username \S+ password)/ && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
/fair-queue individual-limit/ && next;
# sort ip explicit-paths.
if (/^ip explicit-path name (\S+)/) {
my($key) = $1;
my($expath) = $_;
while (<INPUT>) {
tr/\015//d;
last if (/^$prompt/);
last if (/^$prompt/ || ! /^(ip explicit-path name |[
!])/);
if (/^ip explicit-path name (\S+)/) {
ProcessHistory("EXPATH","keysort","$key","$expath");
$key = $1;
$expath = $_;
} else {
$expath .= $_;
}
}
ProcessHistory("EXPATH","keysort","$key","$expath");
}
# sort route-maps
if (/^route-map (\S+)/) {
my($key) = $1;
my($routemap) = $_;
while (<INPUT>) {
tr/\015//d;
last if (/^$prompt/ || ! /^(route-map |[ !])/);
if (/^route-map (\S+)/) {
ProcessHistory("ROUTEMAP","keysort","$key","$routemap");
$key = $1;
$routemap = $_;
} else {
$routemap .= $_;
}
}
ProcessHistory("ROUTEMAP","keysort","$key","$routemap");
}
# filter out any RCS/CVS tags to avoid confusing local CVS
storage
s/\$(Revision|Id):/ $1:/;
# order access-lists
/^access-list\s+(\d\d?)\s+(\S+)\s+(\S+)/ &&
ProcessHistory("ACL $1 $2","ipsort","$3","$_") && next;
# order extended access-lists
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+host\s+(\S+)/ &&
ProcessHistory("EACL $1 $2","ipsort","$3","$_") && next;
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+(\d\S+)/ &&
ProcessHistory("EACL $1 $2","ipsort","$3","$_") && next;
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+any/ &&
ProcessHistory("EACL $1 $2","ipsort","0.0.0.0","$_") &&
next;
# order arp lists
/^arp\s+(\d+\.\d+\.\d+\.\d+)\s+/ &&
ProcessHistory("ARP","ipsort","$1","$_") && next;
/^ip
prefix-list\s+(\S+)\s+seq\s+(\d+)\s+(permit|deny)\s+(\d\S+)(\/.*)$/ &&
ProcessHistory("PACL $1 $3","ipsort","$4","ip prefix-list $1
$3 $4$5\n")
&& next;
# order logging statements
/^logging (\d+\.\d+\.\d+\.\d+)/ &&
ProcessHistory("LOGGING","ipsort","$1","$_") && next;
# order/prune snmp-server host statements
# we only prune lines of the form
# snmp-server host a.b.c.d <community>
if (/^snmp-server host (\d+\.\d+\.\d+\.\d+) /) {
if (defined($ENV{'NOCOMMSTR'})) {
my($ip) = $1;
my($line) = "snmp-server host $ip";
my(@tokens) = split(' ', $');
my($token);
while ($token = shift(@tokens)) {
if ($token eq 'version') {
$line .= " " . join(' ', ($token,
shift(@tokens)));
} elsif ($token =~ /^(informs?|traps?|(no)?auth)$/)
{
$line .= " " . $token;
} else {
$line = "!$line " . join(' ', ("<removed>",
join(' ', at tokens)));
last;
}
}
ProcessHistory("SNMPSERVERHOST","ipsort","$ip","$line\n");
} else {
ProcessHistory("SNMPSERVERHOST","ipsort","$1","$_");
}
next;
}
if (/^(snmp-server community) (\S+)/) {
if (defined($ENV{'NOCOMMSTR'})) {
ProcessHistory("SNMPSERVERCOMM","keysort","$_","!$1
<removed>$'") && next;
} else {
ProcessHistory("SNMPSERVERCOMM","keysort","$_","$_") &&
next;
}
}
# order/prune tacacs/radius server statements
if (/^(tacacs-server|radius-server) key / && $filter_pwds >= 1)
{
ProcessHistory("","","","!$1 key <removed>\n"); next;
}
# order clns host statements
/^clns host \S+ (\S+)/ &&
ProcessHistory("CLNS","keysort","$1","$_") && next;
# order alias statements
/^alias / && ProcessHistory("ALIAS","keysort","$_","$_") &&
next;
# delete ntp auth password - this md5 is a reversable too
if (/^(ntp authentication-key \d+ md5) / && $filter_pwds >= 1) {
ProcessHistory("","","","!$1 <removed>\n"); next;
}
# order ntp peers/servers
if (/^ntp (server|peer) (\d+)\.(\d+)\.(\d+)\.(\d+)/) {
$sortkey = sprintf("$1 %03d%03d%03d%03d",$2,$3,$4,$5);
ProcessHistory("NTP","keysort",$sortkey,"$_");
next;
}
# order ip host line statements
/^ip host line(\d+)/ &&
ProcessHistory("IPHOST","numsort","$1","$_") && next;
# order ip nat source static statements
/^ip nat (\S+) source static (\S+)/ &&
ProcessHistory("IP NAT $1","ipsort","$2","$_") && next;
# order atm map-list statements
/^\s+ip\s+(\d+\.\d+\.\d+\.\d+)\s+atm-vc/ &&
ProcessHistory("ATM map-list","ipsort","$1","$_") && next;
# order ip rcmd lines
/^ip rcmd/ && ProcessHistory("RCMD","keysort","$_","$_") &&
next;
# system controller
/^syscon address (\S*) (\S*)/ &&
ProcessHistory("","","","!syscon address $1 <removed>\n") &&
next;
if (/^syscon password (\S*)/ && $filter_pwds >= 1) {
ProcessHistory("","","","!syscon password <removed>\n");
next;
}
# catch anything that wasnt matched above.
ProcessHistory("","","","$_");
# end of config. the ": " game is for the PIX
if (/^(: +)?end$/ || /CSS.*#/ || /$prompt/ ) {
$found_end = 1;
return(1);
}
}
return(0);
}
# dummy function
sub DoNothing {print STDOUT;}
# Main
%commands=(
'term length 65535' => "TermLength",
'copy profile user-profile' => "CopyProfile",
'show version' => "ShowVersion",
'show boot' => "ShowBoot",
'show run' => "ShowRun"
);
# keys() doesnt return things in the order entered and the order of the
# cmds is important (show version first and show run last). pita
@commands=(
"term length 65535",
"copy profile user-profile",
"show version",
"show boot",
"show run"
);
$cisco_cmds=join(";", at commands);
$cmds_regexp=join("|", at commands);
open(OUTPUT,">$host.new") || die "Can't open $host.new for writing:
$!\n";
select(OUTPUT);
# make OUTPUT unbuffered if debugging
if ($debug) { $| = 1; }
if ($file) {
print STDERR "opening file $host\n" if ($debug);
print STDOUT "opening file $host\n" if ($log);
open(INPUT,"<$host") || die "open failed for $host: $!\n";
} else {
print STDERR "executing clogin -t $timeo -c\"$cisco_cmds\" $host\n"
if ($debug);
print STDOUT "executing clogin -t $timeo -c\"$cisco_cmds\" $host\n"
if ($log);
if (defined($ENV{NOPIPE})) {
system "clogin -t $timeo -c \"$cisco_cmds\" $host </dev/null >
$host.raw 2>&1" || die "clogin failed for $host: $!\n";
open(INPUT, "< $host.raw") || die "clogin failed for $host:
$!\n";
} else {
open(INPUT,"clogin -t $timeo -c \"$cisco_cmds\" $host </dev/null
|") || die "clogin failed for $host: $!\n";
}
}
# determine password filtering mode
if ($ENV{"FILTER_PWDS"} =~ /no/i) {
$filter_pwds = 0;
} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) {
$filter_pwds = 2;
} else {
$filter_pwds = 1;
}
ProcessHistory("","","","!RANCID-CONTENT-TYPE: cisco-css\n!\n");
ProcessHistory("COMMENTS","keysort","B0","!\n");
ProcessHistory("COMMENTS","keysort","F0","!\n");
ProcessHistory("COMMENTS","keysort","G0","!\n");
TOP: while(<INPUT>) {
tr/\015//d;
# if (/\#\s?exit$/) {
if (/\#\s?exit/) {
$clean_run=1;
last;
}
if (/^Error:/) {
print STDOUT ("$host clogin error: $_");
print STDERR ("$host clogin error: $_") if ($debug);
$clean_run=0;
last;
}
while (/#\s*($cmds_regexp)\s*$/) {
$cmd = $1;
if (!defined($prompt)) {$prompt = ($_ =~ /^([^#]+#)/)[0]; }
print STDERR ("HIT COMMAND:$_") if ($debug);
if (! defined($commands{$cmd})) {
print STDERR "$host: found unexpected command - \"$cmd\"\n";
# $clean_run = 0; i'll track down why this is necessary,
but
# last TOP; the bottom line is this gets stuck in a
loop..
next TOP;
} else {
$rval = &{$commands{$cmd}};
delete($commands{$cmd});
if ($rval == -1) {
$clean_run = 0;
last TOP;
}
}
}
}
print STDOUT "Done $logincmd: $_\n" if ($log);
# Flush History
ProcessHistory("","","","");
# Cleanup
close(INPUT);
close(OUTPUT);
if (defined($ENV{NOPIPE})) {
unlink("$host.raw") if (! $debug);
}
# check for completeness
if (scalar(%commands) || !$clean_run || !$found_end) {
if (scalar(%commands)) {
printf(STDOUT "$host: missed cmd(s): %s\n", join(',',
keys(%commands)));
printf(STDERR "$host: missed cmd(s): %s\n", join(',',
keys(%commands))) if ($debug);
}
if (!$clean_run || !$found_end) {
print STDOUT "$host: End of run not found\n";
print STDERR "$host: End of run not found\n" if ($debug);
system("/usr/bin/tail -1 $host.new");
}
unlink "$host.new" if (! $debug);
}
More information about the Rancid-discuss
mailing list