Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
mxtools/mxmirror/mxmirror
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
547 lines (418 sloc)
13.6 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/local/system/perl/bin/perl | |
use Socket; | |
use Sys::Hostname; | |
use Getopt::Long; | |
use strict; | |
use warnings; | |
my $JBODMAP = '/etc/amd/amd.jbod'; | |
my $MIRRORMAP = '/etc/mxmirrors'; | |
my %CONFIG = ( CMD => 'STRING', ); | |
my %opt = ( | |
from => undef, | |
to => undef, | |
check => undef, | |
verbose => 0, | |
debug => 0, | |
); | |
my $fullhostname = hostname; | |
my ($hostname) = $fullhostname =~ /^(.*?)\./; | |
my $hostip = inet_ntoa( scalar gethostbyname($fullhostname) ); | |
my $jbodmap = read_jbodmap($JBODMAP); | |
my $mirrors = read_mirrormap($MIRRORMAP); | |
my $result = GetOptions( | |
"from:s" => \$opt{from}, | |
"to:s" => \$opt{to}, | |
"destinationhosts" => \$opt{destinationhosts}, | |
"sourcehosts" => \$opt{sourcehosts}, | |
"all" => \$opt{all}, | |
"format|fmt:s" => \$opt{format}, | |
"execute" => \$opt{execute}, | |
"command|cmd:s" => \$opt{command}, | |
"check:s" => \$opt{check}, | |
"verbose" => \$opt{verbose}, | |
"debug" => \$opt{debug}, | |
"help" => \$opt{help} | |
); | |
if ( $opt{help} ) { | |
print <<"EOF"; | |
$0 [options] [action] | |
actions: | |
<default> print mirror-cmds to mirror local filesystems | |
--sourcehosts list all hosts to mirror | |
--destinationhosts list all mirror hosts | |
--all list all defined mirrors | |
--check check mirrorstatus of localhost | |
options: | |
--format=<fmt> define output format | |
--execute execute <fmt> if <cmd> is not specified | |
--command=<cmd> execute <cmd> for every host listed | |
--config=<file> read mirror-definitions from <file> | |
EOF | |
exit 0; | |
} | |
if ( defined $opt{destinationhosts} ) { | |
exit print_and_exec_hostlist( mirrors_reverse($mirrors) ); | |
} | |
if ( defined $opt{sourcehosts} ) { | |
exit print_and_exec_hostlist($mirrors); | |
} | |
if ( defined $opt{all} ) { | |
exit print_and_exec_all($mirrors); | |
} | |
if ( defined $opt{check} ) { | |
my $host = $opt{check} || $hostname; | |
my $dest = mirrors_reverse($mirrors); | |
foreach ( @{ $dest->{$host} } ) { | |
printf "RULE: $_->{string}\n" if ( $opt{debug} ); | |
print get_mirror_status($_); | |
printf " $_->{source}->{host}:$_->{source}->{hostpath} $_->{destination}->{host}:$_->{destination}->{hostpath} \t$_->{args}\n"; | |
} | |
exit 0; | |
} | |
my $err = 0; | |
foreach ( @{ $mirrors->{$hostname} } ) { | |
my $default_fmt = $CONFIG{CMD}; | |
my $fmt = ( defined $opt{format} ) ? $opt{format} : $default_fmt; | |
my $cmd = $opt{command}; | |
if ( not defined $cmd and $opt{execute} ) { | |
$cmd = $fmt; | |
} | |
if ( not $fmt or ( defined $cmd and not $cmd ) ) { | |
print "\n"; | |
print " default format: '$default_fmt'\n"; | |
print " default command: <none>\n\n"; | |
print " format/command variables:\n"; | |
print " DSTHOST destination host\n"; | |
print " DSTPATH destination path\n"; | |
print " SRCHOST source host\n"; | |
print " SRCPATH source path\n"; | |
print " ARGS additional args\n"; | |
print " DESTINATION DSTHOST==SRCHOST : DSTHOST:DSTPATH \n"; | |
print " DSTHOST!=SRCHOST : DSTPATH \n\n"; | |
exit 1; | |
} | |
print do_format( $fmt, $_ ) . "\n"; | |
if ($cmd) { | |
my $command = do_format( $cmd, $_ ); | |
if ( system($command) != 0 ) { | |
print STDERR "mxmirror FAILED CMD: $command\n"; | |
$err++; | |
} | |
} | |
} | |
exit($err); | |
################################################################### | |
sub print_and_exec_all { | |
my $list = shift; | |
my $default_fmt = "SRCHOST:SRCPATH DSTHOST:DSTPATH ARGS"; | |
my $fmt = ( defined $opt{format} ) ? $opt{format} : $default_fmt; | |
my $cmd = $opt{command}; | |
if ( not defined $cmd and $opt{execute} ) { | |
$cmd = $fmt; | |
} | |
if ( not $fmt or ( defined $cmd and not $cmd ) ) { | |
print "\n"; | |
print " default format: '$default_fmt'\n"; | |
print " default command: <none>\n\n"; | |
print " format/command variables:\n"; | |
print " DSTHOST destination host\n"; | |
print " DSTPATH destination path\n"; | |
print " SRCHOST source host\n"; | |
print " SRCPATH source path\n"; | |
print " ARGS additional args\n"; | |
print " DESTINATION DSTHOST==SRCHOST : DSTHOST:DSTPATH \n"; | |
print " DSTHOST!=SRCHOST : DSTPATH \n\n"; | |
return 1; | |
} | |
my @hosts = sort keys %{$list}; | |
foreach my $h (@hosts) { | |
my %vars = (); | |
foreach ( @{ $list->{$h} } ) { | |
print do_format( $fmt, $_, \%vars ) . "\n" if ( $opt{format} or not $opt{command} ); | |
if ($cmd) { | |
my $command = do_format( $cmd, $_, \%vars ); | |
print qx($command); | |
} | |
} | |
} | |
return 0; | |
} | |
sub print_and_exec_hostlist { | |
my $list = shift; | |
my $default_fmt = "HOST"; | |
my $fmt = ( defined $opt{format} ) ? $opt{format} : $default_fmt; | |
my $cmd = $opt{command}; | |
if ( not defined $cmd and $opt{execute} ) { | |
$cmd = $fmt; | |
} | |
if ( not $fmt or ( defined $cmd and not $cmd ) ) { | |
print "\n"; | |
print " default format: '$default_fmt'\n"; | |
print " default command: <none>\n\n"; | |
print " format/command variables:\n"; | |
print " HOST destination\n\n"; | |
return 1; | |
} | |
foreach ( sort keys %{$list} ) { | |
print do_format( $fmt, {}, { HOST => $_ } ) . "\n" if ( $opt{format} or not $opt{command} ); | |
if ($cmd) { | |
my $command = do_format( $cmd, {}, { HOST => $_ } ); | |
print qx($command); | |
} | |
} | |
return 0; | |
} | |
sub mirrors_reverse { | |
my $mirrors = shift; | |
my $r; | |
foreach my $h ( sort keys %{$mirrors} ) { | |
foreach ( @{ $mirrors->{$h} } ) { | |
push @{ $r->{ $_->{destination}->{host} } }, $_; | |
} | |
} | |
return $r; | |
} | |
sub get_mirror_status { | |
my $mirror = shift; | |
return 'REMOTE' unless ( $hostname eq $mirror->{destination}->{host} ); | |
if ( open( F, '<', "$mirror->{destination}->{hostpath}/.PMIRROR_STATUS" ) ) { | |
my $line = <F>; | |
close F; | |
my ( $status, $timestamp ) = $line =~ /^(\S+) (\d+) /; | |
if ( $status eq 'OK' ) { | |
return sprintf( "%2.2f", ( time() - $timestamp ) / 24 / 60 / 60 ); | |
} | |
return $line; | |
} | |
return 'UNKNOWN'; | |
} | |
sub do_format { | |
my $fmt = shift; | |
my $data = shift; | |
my $vars = shift || {}; | |
if ( defined $data ) { | |
my $dst | |
= ( $data->{source}->{host} eq $data->{destination}->{host} ) | |
? $data->{destination}->{hostpath} | |
: "$data->{destination}->{host}:$data->{destination}->{hostpath}"; | |
$fmt =~ s/DESTINATION/$dst/g; | |
$fmt =~ s/SRCHOST/$data->{source}->{host}/g; | |
$fmt =~ s/DSTHOST/$data->{destination}->{host}/g; | |
$fmt =~ s/SRCPATH/$data->{source}->{hostpath}/g; | |
$fmt =~ s/DSTPATH/$data->{destination}->{hostpath}/g; | |
$fmt =~ s/ARGS/$data->{args}/g; | |
$fmt =~ s/STRING/$data->{string}/g; | |
# $fmt =~ s/TIME/$data->{timeslot}->{time_str}/g; | |
} | |
foreach ( keys %{$vars} ) { | |
print "XXX" . $_; | |
$fmt =~ s/$_/$vars->{$_}/g; | |
} | |
return $fmt; | |
} | |
sub to_jbod_path { | |
my $host = shift; | |
my $jbod = shift; | |
my $path = shift; | |
#return "/amd/${host}/X/${jbod}${prepend}${path}"; | |
return "/jbod/${jbod}${path}"; | |
} | |
sub to_amd_path { | |
my $host = shift; | |
my $partition = shift; | |
my $path = shift; | |
return "/amd/${host}/${partition}${path}"; | |
} | |
# from := <host1>:<p1><path1> | |
# <path1> must be absolute or empty | |
# <p1> or <path1> must be set | |
# | |
# isset(<p1>): /amd/<host1>/<p1><path1> | |
# isempty(<p1>): <path1> | |
# to := <host2>:<p2><path2> | |
# <path2> must be absolute or empty | |
# <p2> or <path2> must be set | |
# | |
# isemtpy(<path2>): <host2>:/amd/<host2>/<p2>/mirror/(<jbod1>|<host1>/<p1>)<path1> | |
# isset(<p2>): <host2>:/amd/<host2>/<p2><path2> | |
# isempty(<p2>): <host2>:<path2> | |
# | |
# from := <jbod1><path1> | |
# /jbod/<jbod1><path1> | |
# to := <jbod2><path2> | |
# isset(<path2>): /jbod/<jbod2><path2> | |
# isjbod(<from>): /jbod/<jbod2>/mirror/<jbod1><path1> | |
# ishost(<from>): /jbod/<jbod2>/mirror/<host1>/<p1><path1> | |
sub convert_to_sourcepath { | |
my $string = shift; | |
my ( $jbod, $path, $host, $hostpath, $partition ); | |
if ( ( $jbod, $path ) = $string =~ /^([XCMD][\dA-Z]\d\d\d)(.*?)$/ ) { | |
unless ( defined $jbodmap->{$jbod} ) { | |
printf STDERR "**ERROR: unknown jbod: $string\n"; | |
return undef; | |
} | |
$host = $jbodmap->{$jbod}; | |
$hostpath = to_jbod_path( $host, $jbod, $path ); | |
return ( | |
{ host => $host, | |
jbod => $jbod, | |
path => $path, | |
hostpath => $hostpath | |
} | |
); | |
} | |
if ( ( $host, $partition, $path ) = $string =~ /^(.*?):(\d+)(.*?)$/ ) { | |
$hostpath = to_amd_path( $host, $partition, $path ); | |
return ( | |
{ host => $host, | |
partition => $partition, | |
path => $path, | |
hostpath => $hostpath | |
} | |
); | |
} | |
if ( ( $host, $path ) = $string =~ m(^(.*?):(/.+?)$) ) { | |
return ( | |
{ host => $host, | |
path => $path, | |
hostpath => $path | |
} | |
); | |
} | |
return; | |
} | |
# from := <host1>:<p1><path1> | |
# <path1> must be absolute or empty | |
# <p1> or <path1> must be set | |
# | |
# isset(<p1>): /amd/<host1>/<p1><path1> | |
# isempty(<p1>): <path1> | |
# | |
# to := <host2>:<p2><path2> | |
# <path2> must be absolute or empty | |
# <p2> or <path2> must be set | |
# | |
# isemtpy(<path2>): <host2>:/amd/<host2>/<p2>/mirror/(<jbod1>|<host1>/<p1>)<path1> | |
# isset(<p2>): <host2>:/amd/<host2>/<p2><path2> | |
# isempty(<p2>): <host2>:<path2> | |
# | |
# from := <jbod1><path1> | |
# /jbod/<jbod1><path1> | |
# | |
# to := <jbod2><path2> | |
# isset(<path2>): /jbod/<jbod2><path2> | |
# isjbod(<from>): /jbod/<jbod2>/mirror/<jbod1><path1> | |
# ishost(<from>): /jbod/<jbod2>/mirror/<host1>/<p1><path1> | |
sub convert_to_destinationpath { | |
my $string = shift; | |
my $source = shift; | |
my ( $jbod, $path, $host, $hostpath, $partition ); | |
if ( ( $jbod, $path ) = $string =~ /^([XCMD][\dA-Z]\d\d\d)(.*?)$/ ) { | |
unless ( defined $jbodmap->{$jbod} ) { | |
printf STDERR "**ERROR: unknown jbod: $string\n"; | |
return undef; | |
} | |
$host = $jbodmap->{$jbod}; | |
unless ($path) { | |
if ( defined $source->{jbod} ) { | |
$path = "/mirror/$source->{jbod}$source->{path}"; | |
} | |
elsif ( defined $source->{partition} ) { | |
$path = "/mirror/$source->{host}/$source->{partition}$source->{path}"; | |
} | |
else { | |
$path = "/mirror/$source->{host}$source->{path}"; | |
} | |
} | |
$hostpath = to_jbod_path( $host, $jbod, $path ); | |
return ( | |
{ host => $host, | |
jbod => $jbod, | |
path => $path, | |
hostpath => $hostpath | |
} | |
); | |
} | |
# to := <host2>:<p2><path2> | |
# <path2> must be absolute or empty | |
# <p2> or <path2> must be set | |
# | |
# isemtpy(<path2>): <host2>:/amd/<host2>/<p2>/mirror/(<jbod1>|<host1>/<p1>)<path1> | |
# isset(<p2>): <host2>:/amd/<host2>/<p2><path2> | |
# isempty(<p2>): <host2>:<path2> | |
if ( ( $host, $partition, $path ) = $string =~ /^(.*?):(\d+)(.*?)$/ ) { | |
unless ($path) { | |
if ( defined $source->{jbod} ) { | |
$path = "/mirror/$source->{jbod}$source->{path}"; | |
} | |
else { | |
$path = "/mirror/$source->{host}/$source->{partition}$source->{path}"; | |
} | |
} | |
$hostpath = to_amd_path( $host, $partition, $path ); | |
return ( | |
{ host => $host, | |
partition => $partition, | |
path => $path, | |
hostpath => $hostpath | |
} | |
); | |
} | |
if ( ( $host, $path ) = $string =~ m(^(.*?):(/.+?)$) ) { | |
return ( | |
{ host => $host, | |
path => $path, | |
hostpath => $path | |
} | |
); | |
} | |
return; | |
} | |
sub read_mirrormap { | |
my $file = shift; | |
my $map = {}; | |
my $string; | |
my ( $from, $to, $args, $host, $frompath, $topath ); | |
my ( $source, $destination ); | |
open( F, '<', $file ) or die "can't open $file: $?"; | |
foreach (<F>) { | |
s/^\s+//; | |
s/\s+$//; | |
s/\s+/ /; | |
next if ( $_ eq "" ); | |
if (/^#\s*(\S+)\s*=\s*(.*)/) { | |
$CONFIG{$1} = $2; | |
} | |
next if (/^#/); | |
$string = $_; | |
if ( ( $from, $to, $args ) = /^(\S+) (\S+)\s*(.*)$/ ) { | |
$source = convert_to_sourcepath($from); | |
unless ($source) { | |
print STDERR "**ERROR: CAN'T PARSE FROM: $_\n"; | |
next; | |
} | |
$destination = convert_to_destinationpath( $to, $source ); | |
unless ($destination) { | |
print STDERR "**ERROR: CAN'T PARSE TO: $_\n"; | |
next; | |
} | |
push @{ $map->{ $source->{host} } }, { string => $string, source => $source, destination => $destination, args => $args }; | |
} | |
else { | |
print STDERR "**ERROR: CAN'T PARSE: $_\n"; | |
} | |
} | |
close F; | |
return $map; | |
} | |
sub read_jbodmap { | |
my $file = shift; | |
open( F, '<', $file ) or die "can't open $file: $?"; | |
my $map = {}; | |
foreach (<F>) { | |
next unless (m(/amd/(.*?)/[XCMD]/([XCMD][\dA-Z]\d\d\d))); | |
$map->{$2} = $1; | |
} | |
close F; | |
return $map; | |
} |