#!/usr/bin/perl use strict; # hosts - print names of all hosts # # erco@3dsite.com (Greg Ercolano) # # This software is public domain. Please maintain version history. # Please notify me of bugs. Do not remove this header. # Use at your own risk. # # VERS DATE AUTHOR COMMENTS # 1.00 09/20/98 Greg Ercolano Initial version # 1.10 08/23/00 Greg Ercolano Added --rsh, --sh # 1.20 12/02/02 Greg Ercolano Rsh now uses fork/exec, sig handling # x.xx xx/xx/xx - - # # Include 'offline' in keys, if machine is offline # Keyname Key's Meaning # ---------- ----------------------------------------------- # sgi All sgis # linux All linux # prod *Should* have production software installed # noprod Should *not* have production software installed # timeserver Should *not* be configured as a time _client_ # mailserver Should *not* be configured as a mail _client_ # license License server (currently unused?) # adm Administrative machine (currently unused?) $G::hosts = <<"EOF"; # Host Keys Comments backup linux,noprod,adm # Backups server inet1 linux,noprod,adm,server,timeserver # Network server: time/dns/etc ed linux,noprod,adm,server,mailserver # Evil Dead sd linux,noprod,adm,server # Scooby Doo rf1 linux,prod,rf # render farm rf2 linux,prod,rf # render farm rf3 linux,prod,rf # render farm rf4 linux,prod,rf # render farm rf5 linux,prod,rf # render farm rf6 linux,prod,rf # render farm rf7 linux,prod,rf # render farm rf8 linux,prod,rf # render farm rf9 linux,prod,rf # render farm rf10 linux,prod,rf # render farm rf11 linux,prod,rf,offline # render farm boyne sgi,prod,license # octane, License & Alfred server maxa sgi,prod mega sgi,prod exa sgi,prod,offline xena sgi,prod,offline dakini sgi,prod newsgi1 sgi,prod newsgi2 sgi,prod othello sgi,prod,offline ykaneko sgi,prod bwhit sgi,prod EOF # GLOBALS FOR INTERRUPT TRAP my $G_pid = 0; # pid of fork()ed process my $G_time = 0; # time since last ^C # ^C INTERRUPT TRAP sub TrapInt { # KILL BACKGROUNDED PROCESS if ( $G_pid > 0 ) { print STDERR "\n(Killed)"; kill($G_pid, -15); } # TWO INTERRUPTS WITHIN A SHORT TIME? EXIT if ( $G_time && (time() - $G_time) < 2 ) { print STDERR "\nhosts: Exiting (two ^C's within 2 secs)\n"; exit(1); } # SAVE CURRENT TIME FOR NEXT INTERRUPT $G_time = time(); } # FORK AND EXEC # Better than system() at preserving arguments with spaces (eg. rsh) # and doesn't incur an unnecessary shell. # sub ForkExec { $SIG{INT} = *TrapInt; # trap interrupts while process running $G_pid = fork(); # save pid for trap if ( $G_pid == 0 ) # child process? { exec(@_); print STDERR "exec $_[0]: $!\n"; exit(1); } wait(); # parent? wait for child $G_pid = 0; # zero out pid $SIG{INT} = "IGNORE"; # IGNORE prevents single ^C from killing script return($? >> 8); } # MAIN { my $err = 1; my ($rshcmd, $shcmd, @hostkeys); # FLUSH ALL OUTPUT select(STDERR); $|; select(STDOUT); $|; # PARSE ARGS my $t; for ( $t=0; $t<=$#ARGV; $t++ ) { # HELP if ( $ARGV[$t] eq "--help" ) { HelpAndExit(); } # LIST KEYS if ( $ARGV[$t] eq "--list" ) { print $G::hosts; exit(0); } # RSH if ( $ARGV[$t] eq "--rsh" ) { $rshcmd = $ARGV[++$t]; if ( $rshcmd eq "" ) { HelpAndExit(); } next; } # SH if ( $ARGV[$t] eq "--sh" ) { $shcmd = $ARGV[++$t]; if ( $shcmd eq "" ) { HelpAndExit(); } next; } # ALL ELSE: HOST KEYS push(@hostkeys, $ARGV[$t]); } # COMPILE HOST DATA INTO A HASH my %host; foreach ( split("\n", $G::hosts ) ) { if ( /^#/ || /^$/ ) { next; } # skip comment or blank lines s/\s+/ /; # fold multiple white into single my @words = split(/\s+/); $host{$words[0]}{'keys'} = join(",",@words); $host{$words[0]}{'comment'} = $_; $host{$words[0]}{'comment'} =~ s/^[^#]*#\s+//; } # LOOP THRU ALL HOSTS my $name; foreach $name ( sort ( keys ( %host ) ) ) { my $yes = -1; # no postives specified yet my $no = 0; # negatives off unless specified my $arg; foreach $arg ( @hostkeys ) { if ( $arg eq "" ) # skip previously parsed args { next; } # HANDLE POSITIVE MATCHES if ( $arg !~ /^-/ ) { if ( $yes == -1 ) # assume off unless positive match { $yes = 0; } my $key; foreach $key ( split(/,/,$host{$name}{'keys'} ) ) { if ( $key eq $arg ) { $yes = 1; } } } # HANDLE NEGATIVE MATCHES elsif ( $arg =~ /^-(.*)/ ) { my $nokey = $1; my $key; foreach $key ( split(/,/,$host{$name}{'keys'} ) ) { $no = (($nokey eq $key)?1:$no); } } } # 'NO' HAS PRECEDENCE if ( $no ) { next; } # 'YES' IF NO POSITIVES SPECIFIED, OR IF MATCH if ( $yes == -1 || # no positives specified? Assume all $yes == 1) # positive specified and passed? { if ( $rshcmd ne "" ) { printf(STDERR "%10s: ", $name); if ( ForkExec("rsh", $name, sprintf($rshcmd,$name,$name,$name,$name)) ) { $err = 1; } print STDERR "\n"; } elsif ( $shcmd ne "" ) { printf(STDERR "%10s: ", $name); if ( system(sprintf($shcmd,$name,$name,$name,$name)) ) { $err = 1; } print STDERR "\n"; } else { print "$name\n"; } } } exit($err); } # HELP sub HelpAndExit() { print <<"EOF"; USAGE hosts # lists all hosts hosts --list # list all hosts with keys hosts [[-]key ..] # list hosts with limits hosts --sh 'cmd' # run 'cmd' in local shell (%s=hostname) hosts --rsh 'cmd' # run 'cmd' in remote shell (%s=hostname) EXAMPLES hosts # print all hosts hosts --list # print all hosts with keys hosts prod # print all production hosts (ie. non-servers) hosts sgi # print all sgis hosts sgi -mega # print all sgis except mega hosts sgi -offline # print all except offline hosts hosts sgi prod # print all sgi production machines hosts linux prod # print all linux production machines REAL WORLD hosts prod --sh 'rdist -c /usr/nreal/license.dat %s:/usr/nreal/license.dat' # rdist the license file to all prod machines hosts --rsh 'date; uptime' # run several commands on remotes hosts --rsh 'perl -e '\\''system("date; hostname");'\\' # run one liner perl command on remotes.. # quoting style is same for a plain rsh(1) CAVEATS With --rsh, the following ^C behavior will be in effect: o Hitting ^C with at least 2 secs between each only kills the current rsh; hosts(1) will move on to the next host. o Two ^C's in a row kills hosts(1). EOF exit(1); }