#! @PERL@
-# Copyright (c) 2008 Zmanda Inc. All Rights Reserved.
+# Copyright (c) 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
# This is a tool to examine a device and generate a reasonable tapetype
use Amanda::MainLoop;
use Amanda::Xfer;
use Amanda::Constants;
-use Amanda::Types;
+use Amanda::Header;
# command-line options
my $opt_only_compression = 0;
my $opt_force = 0;
my $opt_label = "amtapetype-".(int rand 2**31);
my $opt_device_name;
+my $opt_config;
+my $opt_property;
# global "hint" from the compression heuristic as to how fast this
# drive is.
my $device_speed_estimate;
-# open up a device, optionally check its label, and start it in ACCESS_WRITE.
+# open up a device, optionally check its label on the first invocation,
+# and start it in ACCESS_WRITE.
+my $_label_checked = 0;
sub open_device {
my $device = Amanda::Device->new($opt_device_name);
if ($device->status() != $DEVICE_STATUS_SUCCESS) {
die("Could not open device $opt_device_name: ".$device->error()."\n");
}
+ if (!$device->configure(0)) {
+ die("Errors configuring $opt_device_name: " . $device->error_or_status());
+ }
+
if (defined $opt_blocksize) {
$device->property_set('BLOCK_SIZE', $opt_blocksize)
or die "Error setting blocksize: " . $device->error_or_status();
}
- if (!$opt_force) {
+ if (!$opt_force and !$_label_checked) {
my $read_label_status = $device->read_label();
if ($read_label_status & $DEVICE_STATUS_VOLUME_UNLABELED) {
- if ($device->volume_label) {
- die "Volume in device $opt_device_name has Amanda label '" .
- {$device->volume_label} . "'. Giving up.";
- }
+ # unlabeled is OK
} elsif ($read_label_status != $DEVICE_STATUS_SUCCESS) {
die "Error reading label: " . $device->error_or_status();
+ } elsif ($device->volume_label) {
+ die "Volume in device $opt_device_name has Amanda label '" .
+ $device->volume_label . "'. Giving up.";
}
+ $_label_checked = 1;
}
- return $device;
-}
+ my $start_time = time;
+ my $retries = 0;
+ my $backoff = 1;
+ while (1) {
+ last if ($device->start($ACCESS_WRITE, $opt_label, undef));
+ if (!($device->status & $DEVICE_STATUS_DEVICE_BUSY)) {
+ die("Error writing label '$opt_label': ". $device->error_or_status());
+ }
-sub start_device {
- my ($device) = @_;
+ if ($retries == 0) {
+ print STDERR "Device is busy. Amtapetype will retry forever; hit ctrl-C to quit.\n";
+ }
+
+ sleep($backoff);
+ $backoff *= 2;
+ $backoff = 120 if $backoff > 120;
+ $retries++;
+ }
- if (!$device->start($ACCESS_WRITE, $opt_label, undef)) {
- die("Error writing label '$opt_label': ". $device->error_or_status());
+ if ($retries) {
+ my $elapsed = time - $start_time;
+ print STDERR "Drive was busy for $elapsed seconds.\n";
+ print STDERR "If this device is used in a changer, you may want to set timeouts appropriately.\n";
}
return $device;
my $pattern = $options{'PATTERN'} || 'FIXED';
my $max_time = $options{'MAX_TIME'} || 0;
+ # get the block size now, while the device is still working
+ my $block_size = $device->property_get("block_size");
+
# start the device
- my $hdr = Amanda::Types::dumpfile_t->new();
- $hdr->{type} = $Amanda::Types::F_DUMPFILE;
+ my $hdr = Amanda::Header->new();
+ $hdr->{type} = $Amanda::Header::F_DUMPFILE;
$hdr->{name} = "amtapetype";
$hdr->{disk} = "/test";
$hdr->{datestamp} = "X";
+ $hdr->{program} = "AMTAPETYPE";
$device->start_file($hdr)
or return $device->error_or_status();
$xfer = Amanda::Xfer->new([$source, $dest]);
# set up the relevant callbacks
- my ($timeout_src, $xfer_src, $spinner_src);
+ my ($timeout_src, $spinner_src);
my $got_error = 0;
my $got_timeout = 0;
- $xfer_src = $xfer->get_source();
- $xfer_src->set_callback(sub {
- my ($src, $xmsg, $xfer) = @_;
- if ($xmsg->{type} == $Amanda::Xfer::XMSG_ERROR) {
- $got_error = $xmsg->{message};
- }
- if ($xfer->get_status() == $Amanda::Xfer::XFER_DONE) {
- Amanda::MainLoop::quit();
- }
- });
-
if ($max_time) {
$timeout_src = Amanda::MainLoop::timeout_source($max_time * 1000);
$timeout_src->set_callback(sub {
my $start_time = time();
- $xfer->start();
+ $xfer->start(sub {
+ my ($src, $xmsg, $xfer) = @_;
+ if ($xmsg->{type} == $Amanda::Xfer::XMSG_ERROR) {
+ $got_error = $xmsg->{message};
+ } elsif ($xmsg->{'type'} == $Amanda::Xfer::XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ });
+
Amanda::MainLoop::run();
- $xfer_src->remove();
$spinner_src->remove();
$timeout_src->remove() if ($timeout_src);
print STDERR " " x 60, "\r";
# OK, we finished, update statistics (even if we saw an error)
my $blocks_written = $device->block();
- my $block_size = $device->property_get("block_size");
$stats->{$pattern}->{BYTES} += $blocks_written * $block_size;
$stats->{$pattern}->{FILES} += 1;
$stats->{$pattern}->{TIME} += $duration;
+ # make sure the time is nonzero
+ if ($stats->{$pattern}->{TIME} == 0) {
+ $stats->{$pattern}->{TIME}++;
+ }
+
if ($device->status() != $Amanda::Device::DEVICE_STATUS_SUCCESS) {
return $device->error_or_status();
}
}
sub check_compression {
- my ($device) = @_;
+ my $device = open_device();
# Check compression status here by property query. If the device can answer
# the question, there's no reason to investigate further.
my $stats = { };
- start_device($device);
-
my $err = write_one_file(
DEVICE => $device,
STATS => $stats,
if ($err != 'TIMEOUT') {
die $err;
}
+ $device->finish();
+
+ # speed calculations are a little tricky: BigInt * float comes out to NaN, so we
+ # cast the BigInts to float first
+ my $random_speed = ($stats->{RANDOM}->{BYTES} . "") / $stats->{RANDOM}->{TIME};
+ print STDERR "Wrote random (uncompressible) data at $random_speed bytes/sec\n";
- # restart the device to rewind it
- start_device($device);
+ # sock this away for make_tapetype's use
+ $device_speed_estimate = $random_speed;
+
+ # restart the device to clear any errors and rewind it
+ $device = open_device();
$err = write_one_file(
DEVICE => $device,
if ($err) {
die $err;
}
+ $device->finish();
- # speed calculations are a little tricky: BigInt * float comes out to NaN, so we
- # cast the BigInts to float first
- my $random_speed = ($stats->{RANDOM}->{BYTES} . "") / $stats->{RANDOM}->{TIME};
my $fixed_speed = ($stats->{FIXED}->{BYTES} . "") / $stats->{FIXED}->{TIME};
-
- print STDERR "Wrote random (uncompressible) data at $random_speed bytes/sec\n";
print STDERR "Wrote fixed (compressible) data at $fixed_speed bytes/sec\n";
- # sock this away for make_tapetype's use
- $device_speed_estimate = $random_speed;
-
$compression_enabled =
($fixed_speed / $random_speed > $compression_check_min_ratio);
return $compression_enabled;
}
+sub data_to_null {
+ my ($device) = @_;
+ my $got_error = 0;
+
+ my $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Device->new($device),
+ Amanda::Xfer::Dest::Null->new(0),
+ ]);
+
+ $xfer->start(sub {
+ my ($src, $xmsg, $xfer) = @_;
+ if ($xmsg->{type} == $Amanda::Xfer::XMSG_ERROR) {
+ $got_error = $xmsg->{message};
+ } elsif ($xmsg->{'type'} == $Amanda::Xfer::XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ });
+
+ Amanda::MainLoop::run();
+}
+
+sub check_property {
+ my $device = open_device();
+
+ print STDERR "Checking for FSF_AFTER_FILEMARK requirement\n";
+ my $fsf_after_filemark = $device->property_get("FSF_AFTER_FILEMARK");
+
+ # not a 'tape:' device
+ return if !defined $fsf_after_filemark;
+
+ $device->start($ACCESS_WRITE, "TEST-001", "20080706050403");
+
+ my $hdr = new Amanda::Header;
+
+ $hdr->{type} = $Amanda::Header::F_DUMPFILE;
+ $hdr->{name} = "localhost";
+ $hdr->{disk} = "/test1";
+ $hdr->{datestamp} = "20080706050403";
+ $hdr->{program} = "AMTAPETYPE";
+ $device->start_file($hdr);
+ $device->finish_file();
+
+ $hdr->{type} = $Amanda::Header::F_DUMPFILE;
+ $hdr->{name} = "localhost";
+ $hdr->{disk} = "/test2";
+ $hdr->{datestamp} = "20080706050403";
+ $hdr->{program} = "AMTAPETYPE";
+ $device->start_file($hdr);
+ $device->finish_file();
+
+ $hdr->{type} = $Amanda::Header::F_DUMPFILE;
+ $hdr->{name} = "localhost";
+ $hdr->{disk} = "/test3";
+ $hdr->{datestamp} = "20080706050403";
+ $hdr->{program} = "AMTAPETYPE";
+ $device->start_file($hdr);
+ $device->finish_file();
+
+ $device->finish();
+
+ #set fsf_after_filemark to false
+ $device->property_set('FSF_AFTER_FILEMARK', 0)
+ or die "Error setting FSF_AFTER_FILEMARK: " . $device->error_or_status();
+
+ my $need_fsf_after_filemark = 0;
+
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(1);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(1) failed");
+ }
+ if ($hdr->{disk} ne "/test1") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test1");
+ }
+ data_to_null($device);
+
+ $hdr = $device->seek_file(2);
+ if ($device->status() == $DEVICE_STATUS_SUCCESS) {
+ if ($hdr->{disk} ne "/test2") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test2");
+ }
+ data_to_null($device);
+
+ $hdr = $device->seek_file(3);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die("seek_file(3) failed");
+ }
+ if ($hdr->{disk} ne "/test3") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test3");
+ }
+ data_to_null($device);
+
+ $device->finish();
+ } else {
+ $need_fsf_after_filemark = 1;
+
+ # $device is in error, so open a new one
+ $device->finish();
+ $device = open_device();
+ }
+
+ #verify need_fsf_after_filemark
+ my $fsf_after_filemark_works = 0;
+ if ($need_fsf_after_filemark) {
+ #set fsf_after_filemark to true
+ $device->property_set('FSF_AFTER_FILEMARK', 1)
+ or die "Error setting FSF_AFTER_FILEMARK: " . $device->error_or_status();
+
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(1);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(1) failed");
+ }
+ if ($hdr->{disk} ne "/test1") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test1");
+ }
+ data_to_null($device);
+
+ $hdr = $device->seek_file(2);
+ if ($device->status() == $DEVICE_STATUS_SUCCESS) {
+ if ($hdr->{disk} ne "/test2") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test2");
+ }
+ data_to_null($device);
+
+ $hdr = $device->seek_file(3);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die("seek_file(3) failed");
+ }
+ if ($hdr->{disk} ne "/test3") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test3");
+ }
+ data_to_null($device);
+ $fsf_after_filemark_works = 1;
+ } else {
+ die("seek_file(2) failed");
+ }
+ $device->finish();
+ }
+
+ if ($need_fsf_after_filemark == 0 && $fsf_after_filemark_works == 0) {
+ if (defined $opt_property || $fsf_after_filemark) {
+ print STDOUT "device_property \"FSF_AFTER_FILEMARK\" \"false\"\n";
+ }
+ $device->property_set('FSF_AFTER_FILEMARK', 0);
+ } elsif ($need_fsf_after_filemark == 1 && $fsf_after_filemark_works == 1) {
+ if (defined $opt_property || !$fsf_after_filemark) {
+ print STDOUT "device_property \"FSF_AFTER_FILEMARK\" \"true\"\n";
+ }
+ $device->property_set('FSF_AFTER_FILEMARK', 1);
+ } else {
+ die ("Broken seek_file");
+ }
+
+ #Check seek to file 1 from header
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(1);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(1) failed");
+ }
+ if ($hdr->{disk} ne "/test1") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test1");
+ }
+ $device->finish();
+
+ #Check seek to file 2 from header
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(2);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(2) failed");
+ }
+ if ($hdr->{disk} ne "/test2") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test1");
+ }
+ $device->finish();
+
+ #Check seek to file 3 from header
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(3);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(3) failed");
+ }
+ if ($hdr->{disk} ne "/test3") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test1");
+ }
+ $device->finish();
+
+ #Check seek to file 3 from eof of 1
+ if ($device->read_label() != $DEVICE_STATUS_SUCCESS) {
+ die ("Could not read label from: " . $device->error_or_status());
+ }
+ if ($device->volume_label != "TEST-001") {
+ die ("wrong label: ", $device->volume_label);
+ }
+ $device->start($ACCESS_READ, undef, undef)
+ or die ("Could not start device: " . $device->error_or_status());
+
+ $hdr = $device->seek_file(1);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(1) failed");
+ }
+ data_to_null($device);
+ $hdr = $device->seek_file(3);
+ if ($device->status() != $DEVICE_STATUS_SUCCESS) {
+ die ("seek_file(3) failed");
+ }
+ if ($hdr->{disk} ne "/test3") {
+ die ("Wrong disk: " . $hdr->{disk} . " expected /test3");
+ }
+ $device->finish();
+}
+
sub make_tapetype {
- my ($device, $compression_enabled) = @_;
+ my ($compression_enabled) = @_;
+
+ my $device = open_device();
my $blocksize = $device->property_get("BLOCK_SIZE");
# First, write one very long file to get the total tape length
print STDERR "Writing one file to fill the volume.\n";
my $stats = {};
- start_device($device);
my $err = write_one_file(
DEVICE => $device,
STATS => $stats,
PATTERN => 'RANDOM');
- if ($stats->{RANDOM}->{BYTES} < 1024 * 1024 * 100) {
- die "Wrote less than 100MB to the device: $err\n";
+ # if we wrote almost no data, then there's probably a problem
+ # with the device, so error out
+ if ($stats->{RANDOM}->{BYTES} < 1024 * 1024) {
+ die "Wrote less than 1MB to the device: $err\n";
}
my $volume_size_estimate = $stats->{RANDOM}->{BYTES};
my $speed_estimate = (($stats->{RANDOM}->{BYTES}."") / 1024)
$file_size -= $file_size % $blocksize;
print STDERR "Writing smaller files ($file_size bytes) to determine filemark.\n";
+ $device->finish();
+ $device = open_device(); # re-open to rewind and clear errors
$stats = {};
- start_device($device);
while (!write_one_file(
DEVICE => $device,
STATS => $stats,
sub usage {
print STDERR <<EOF;
Usage: amtapetype [-h] [-c] [-f] [-b blocksize] [-t typename] [-l label]
- [ [-o config_overwrite] ... ] device
+ [ [-o config_override] ... ] [config] device
-h Display this message
-c Only check hardware compression state
-f Run amtapetype even if the loaded volume is already in use
-b Blocksize to use (default 32k)
-t Name to give to the new tapetype definition
-l Label to write to the tape (default is randomly generated)
+ -p Check property of the device.
-o Overwrite configuration parameter (such as device properties)
Blocksize can include an optional suffix (k, m, or g)
+
+ If CONFIG is specified, the device and its configuration are loaded
+ from the correspnding amanda.conf.
EOF
exit(1);
}
Amanda::Util::setup_application("amtapetype", "server", $CONTEXT_CMDLINE);
config_init(0, undef);
-my $config_overwrites = new_config_overwrites($#ARGV+1);
+my $config_overrides = new_config_overrides($#ARGV+1);
Getopt::Long::Configure(qw(bundling));
GetOptions(
't=s' => \$opt_tapetype_name,
'f' => \$opt_force,
'l' => \$opt_label,
- 'o=s' => sub { add_config_overwrite_opt($config_overwrites, $_[1]); },
+ 'p' => \$opt_property,
+ 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
) or usage();
-usage() if (@ARGV != 1);
-
+usage() if (@ARGV < 1 or @ARGV > 2);
+
+set_config_overrides($config_overrides);
+if (@ARGV == 2) {
+ $opt_config = shift @ARGV;
+ config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
+} else {
+ config_init(0, undef);
+}
$opt_device_name= shift @ARGV;
-apply_config_overwrites($config_overwrites);
my ($cfgerr_level, @cfgerr_errors) = config_errors();
if ($cfgerr_level >= $CFGERR_WARNINGS) {
config_print_errors();
if ($cfgerr_level >= $CFGERR_ERRORS) {
- die("errors processing configuration options");
+ die("errors processing config file");
}
}
Amanda::Util::finish_setup($RUNNING_AS_ANY);
-my $device = open_device();
+# Find property of the device.
+check_property();
-my $compression_enabled = check_compression($device);
-print STDERR "Compression: ",
- $compression_enabled? "enabled" : "disabled",
- "\n";
+if (!defined $opt_property) {
+ my $compression_enabled = check_compression();
+ print STDERR "Compression: ",
+ $compression_enabled? "enabled" : "disabled",
+ "\n";
-if ($compression_enabled and !$opt_force) {
- print STDERR "Turn off compression or run amtapetype with the -f option\n";
- exit(1);
-}
+ if ($compression_enabled and !$opt_force) {
+ print STDERR "Turn off compression or run amtapetype with the -f option\n";
+ exit(1);
+ }
-if (!$opt_only_compression) {
- make_tapetype($device, $compression_enabled);
+ if (!$opt_only_compression) {
+ make_tapetype($compression_enabled);
+ }
}
+
+Amanda::Util::finish_application();