#!/bin/perl -w ############################################################################# # # NOTE: This file under revision control using RCS # Any changes made without RCS will be lost # # $Source: /home/nickb/perl/backup-scripts/RCS/rmtcopy-0.11,v $ # $Revision: 1.1 $ # $Date: 2001/11/09 22:04:37 $ # $Author: nickb $ # $Locker: $ # $State: Exp $ # # Purpose: # # Description: # # Directions: 'perldoc rmtcopy' # # Default Location: # # Invoked by: # # # Depends on: # # Copyright (c) 2001 Assentive Solutions. All rights reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation available at # # http://www.gnu.org/copyleft/gpl.html # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # ############################################################################# use 5.6.0; use File::Basename "basename"; use Getopt::Long; use Sys::Hostname; use strict; use vars '$VERSION'; $VERSION = '0.11'; my $usage = q/ rmtcopy --help rmtcopy -v rmtcopy --src [srchost:]device --dest [desthost:]device --block-size [ --dd
] [ --rsh ] [ --verbose ] [ --debug ]/ . "\n\n"; my $version = qq/rmtcopy version $VERSION, Copyright (C) 2001 Assentive Solutions This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\n/; die $usage if (@ARGV) == 0; # paths my @DDPATH = qw( /bin /usr/bin /usr/local/bin ); my @RSHPATH = @DDPATH; # constants my $BASENAME = basename $0; my $HOSTNAME = hostname(); # flags my $DESTFILE = 0; my $LOCAL_DESTHOST = 0; my $LOCAL_SRCHOST = 0; # program-wide vars my ($BLOCK_SIZE, $CMD, $CP_IN, $CP_OUT, $DEBUG, $DEBUG_LEVEL, $DEST, $DESTDEV, $DESTHOST, $DD, $HELP, $RSH, $SRC, $SRCDEV, $SRCHOST, $v, $VERBOSE); GetOptions( 'block-size=i' => \$BLOCK_SIZE,# block size for dd 'debug!' => \$DEBUG, # debug 'dest=s' => \$DEST, # destination host and device 'dd=s' => \$DD, # dd binary 'help' => \$HELP, # display usage 'rsh=s' => \$RSH, # rsh binary 'src=s' => \$SRC, # source host and device 'v' => \$v, # program version 'verbose!' => \$VERBOSE # display warnings ); # -- sig handlers -- $SIG{INT} = 'IGNORE'; $SIG{PIPE} = sub { die "$BASENAME: broken pipe: $!\n" }; my $TRAP = 'trap "" 0 1 2 3 15 17 18'; # ----------------------------------------------------------------------- # main # ----------------------------------------------------------------------- # set debug level $DEBUG_LEVEL = &setDebugLevel; warn "$BASENAME: debug level $DEBUG_LEVEL\n" if $DEBUG_LEVEL > 1; # check cmd-line parameters &chkOpts; # set cmd &setCmd; # run cmd &runCopy; # ----------------------------------------------------------------------- # subroutines # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # setDebugLevel: set debug level: none, verbose, or debug # caller: main # parameters: # returns: 0, 1, or 2 # ----------------------------------------------------------------------- sub setDebugLevel { ## -- check opts -- if (defined($DEBUG)) # $DEBUG set on cmd-line to 0 or 1 { if ($DEBUG) # $DEBUG = 1 { return 2; # full debug } } if (defined($VERBOSE)) # $VERBOSE set on cmd-line to 0 or 1 { if ($VERBOSE) # $VERBOSE = 1 { return 1; # partial debug } else { # $VERBOSE = 0 return 0; # no debug } } } # ----------------------------------------------------------------------- # chkOpts: check cmd-line args # caller: main # parameters: # returns: # ----------------------------------------------------------------------- sub chkOpts { die $version if $v; die $usage if $HELP; my $dir; # -- source host & device -- if ($SRC) { if ($SRC =~ /(^\w+.*):(\/dev\/\w+.*$)/) # remote host and device { $SRCHOST = $1; $SRCDEV = $2; $LOCAL_SRCHOST = 1 if $SRCHOST eq $HOSTNAME; if ($LOCAL_SRCHOST) { die "$BASENAME: no device $SRCDEV\n" unless -e $SRCDEV; warn "chkOpts(): src host is local\n" if $DEBUG_LEVEL > 1; } warn "chkOpts(): set src host $SRCHOST\n" if $DEBUG_LEVEL > 1; warn "chkOpts(): set src $SRCDEV\n" if $DEBUG_LEVEL > 1; } elsif ($SRC =~ /^\/dev\/\w+.*$/) { # local device $LOCAL_SRCHOST = 1; warn "chkOpts(): src host is local\n" if $DEBUG_LEVEL > 1; if (-e $SRC) { $SRCDEV = $SRC; warn "chkOpts(): set src $SRCDEV\n" if $DEBUG_LEVEL > 1; } else { die "$BASENAME: no device $SRCDEV\n"; } } else { die "$BASENAME: invalid src specification\n"; } } else { $LOCAL_SRCHOST = 1; # assume /dev/rmt/0 on local srchost $SRCDEV = '/dev/rmt/0'; warn "chkOpts(): src host is local\n" if $DEBUG_LEVEL > 1; warn "chkOpts(): set src $SRCDEV\n" if $DEBUG_LEVEL > 1; } # -- dest host & device. may be disk file -- if ($DEST) { if ($DEST =~ /(^\w+.*):(\/dev\/\w+.*$)/) # remote device { $DESTHOST = $1; $DESTDEV = $2; $LOCAL_DESTHOST = 1 if $DESTHOST eq $HOSTNAME; if ($LOCAL_DESTHOST) { die "$BASENAME: no device $DESTDEV\n" unless -e $DESTDEV; warn "chkOpts(): dest host is local\n" if $DEBUG_LEVEL > 1; } warn "chkOpts(): set dest host $DESTHOST\n" if $DEBUG_LEVEL > 1; warn "chkOpts(): set dest $DESTDEV\n" if $DEBUG_LEVEL > 1; } elsif ($DEST =~ /(^\w+.*):(\/\w+.*$)/) { # remote disk file $DESTHOST = $1; $DESTDEV = $2; $DESTFILE = 1; $LOCAL_DESTHOST = 1 if $DESTHOST eq $HOSTNAME; if ($LOCAL_DESTHOST) { warn "chkOpts(): dest host is local\n" if $DEBUG_LEVEL > 1; } } elsif ($DEST =~ /^\/dev\/\w+.*$/) { # local device $LOCAL_DESTHOST = 1; warn "chkOpts(): dest host is local\n" if $DEBUG_LEVEL > 1; if (-e $DEST) { $DESTDEV = $DEST; warn "chkOpts(): set dest $DESTDEV\n" if $DEBUG_LEVEL > 1; } else { die "$BASENAME: no device $DESTDEV\n"; } } elsif ($DEST =~ /^\/\w+.*$/) { # local disk file $DESTDEV = $DEST; $DESTFILE = 1; $LOCAL_DESTHOST = 1; warn "chkOpts(): dest host is local\n" if $DEBUG_LEVEL > 1; warn "chkOpts(): set dest $DESTDEV\n" if $DEBUG_LEVEL > 1; } else { die "$BASENAME: invalid dest specification\n"; } } else { $LOCAL_DESTHOST = 1; # assume /dev/rmt/0 on local desthost $DESTDEV = '/dev/rmt/0'; warn "chkOpts(): dest host is local\n" if $DEBUG_LEVEL > 1; warn "chkOpts(): set dest $DESTDEV\n" if $DEBUG_LEVEL > 1; } if (defined($SRCHOST) && defined($DESTHOST)) { if ($LOCAL_SRCHOST and $LOCAL_DESTHOST && $SRCDEV eq $DESTDEV) { die "$BASENAME: host and device overlap\n"; } elsif ($SRCHOST eq $DESTHOST && $SRCDEV eq $DESTDEV) { die "$BASENAME: host and device overlap\n"; } } # -- block size -- if ($BLOCK_SIZE) { die "$BASENAME: block size out of range\n" if $BLOCK_SIZE < 1; warn "chkOpts(): set block size $BLOCK_SIZE\n" if $DEBUG_LEVEL > 1; } else { die "$BASENAME: block size undefined. use --block-size to specify\n"; } # -- dd binary -- if ($LOCAL_DESTHOST or $LOCAL_SRCHOST) { if ($DD) { die "$BASENAME: $DD not found: $!\n" unless -e $DD; die "$BASENAME: $DD not executable: $!\n" unless -x $DD; } else { foreach $dir (@DDPATH) { if (-x "$dir/dd") { $DD = "$dir/dd"; last; } } die "$BASENAME: cannot find dd. use --dd to specify\n" unless defined $DD; } warn "chkOpts(): set local dd to $DD\n" if $DEBUG_LEVEL > 1; } else { $DD = '/bin/dd'; warn "chkOpts(): assuming remote /bin/dd\n" if $DEBUG_LEVEL > 1; } # -- rsh binary -- if ($RSH) { die "$BASENAME: $RSH not found: $!\n" unless -e $RSH; die "$BASENAME: $RSH not executable: $!\n" unless -x $RSH; } else { foreach $dir (@RSHPATH) { if (-x "$dir/rsh") { $RSH = "$dir/rsh"; last; } elsif (-x "$dir/ssh") { $RSH = "$dir/ssh"; last; } else { next; } } die "$BASENAME: cannot find rsh. use --rsh to specify\n" unless defined $RSH; } warn "chkOpts(): using $RSH for transport\n" if $DEBUG_LEVEL > 1; } # ----------------------------------------------------------------------- # setCmd: set dd cmds for copying # caller: main # parameters: # returns: # ----------------------------------------------------------------------- sub setCmd { # -- copy in -- if ($LOCAL_SRCHOST) { $CP_IN = "$TRAP\; $DD if=$SRCDEV ibs=$BLOCK_SIZE"; } else { $CP_IN = "\($TRAP\; $RSH $SRCHOST \'$TRAP\; $DD if=$SRCDEV ibs=$BLOCK_SIZE\'\)"; } # -- copy out -- if ($LOCAL_DESTHOST && $DESTFILE) { $CP_OUT = "$DD of=$DESTDEV"; } elsif ($LOCAL_DESTHOST) { $CP_OUT = "$DD of=$DESTDEV obs=$BLOCK_SIZE"; } elsif ($DESTFILE) { $CP_OUT = "\($TRAP\; $RSH $DESTHOST \'$TRAP\; $DD of=$DESTDEV\'\)"; } else { $CP_OUT = "\($TRAP\; $RSH $DESTHOST \'$TRAP\; $DD of=$DESTDEV obs=$BLOCK_SIZE conv=noerror\'\)"; } } # ----------------------------------------------------------------------- # runCopy: run dd cmds # caller: main # parameters: # returns: # ----------------------------------------------------------------------- sub runCopy { my $cmd = "$CP_IN | $CP_OUT"; warn "runCopy(): running cmd \'$cmd\'\n" if $DEBUG_LEVEL >= 1; system($cmd); } # ----------------------------------------------------------------------- # documentation # ----------------------------------------------------------------------- =head1 NAME rmtcopy - copy remote tapes using dd pipe =head1 SYNOPSIS B B<--help> B B<-v> B [ B<--src> [B]B ] [ B<--dest> [B]B ] B<--block-size> EbytesE [ B<--dd> Edd binaryE ] [ B<--rsh> Ersh|ssh binaryE ] [ B<--verbose> ] [ B<--debug> ] =head1 DESCRIPTION I copies tapes from one remote tape drive to another using dd(1) and an rsh(1) or ssh(1) pipe. B can also copy a remote tape to a local disk file, or copy a local tape to a remote disk file. =head1 OPTIONS =over 4 =item B<--help> display usage and exit. =item B<-v> display program version and exit. =item B<--src> source host. defaults to /dev/rmt/0 on the local host. may be followed by a device specification. =item B<--dest> destination host. defaults to /dev/rmt/0 on the local host. may be followed by a device specification. =item B<--block-size> block size of source tape archive for dd, in bytes. must be positive integer and must be defined. =item B<--dd> absolute path of local dd(1) binary. defaults to /bin/dd. =item B<--rsh> absolute path of rsh(1) or replacement (ssh). defaults to /bin/rsh. =item B<--verbose> display verbose output to STDERR. =item B<--debug> display debugging output to STDERR. =back =head1 README copies remote tapes using dd and ssh pipe =head1 PREREQUISITES perl 5.6.0 or newer =head1 VERSION B v0.11, Copyright (C) 2001 Assentive Solutions =head1 AUTHOR Nick Balthaser =head1 OSNAMES freebsd solaris =head1 BUGS =over 4 =item * a cpio(1L) archive that spans multiple volumes may cause dd to hang up unpredictably. =back =head1 SCRIPT CATEGORIES UNIX/System_administration =cut