#!/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