1#! @PERL@ 2# Copyright (c) 2009-2013 Zmanda, Inc. All Rights Reserved. 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU General Public License 6# as published by the Free Software Foundation; either version 2 7# of the License, or (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, but 10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12# for more details. 13# 14# You should have received a copy of the GNU General Public License along 15# with this program; if not, write to the Free Software Foundation, Inc., 16# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17# 18# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300 19# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com 20 21use lib '@amperldir@'; 22use strict; 23use warnings; 24 25use File::Basename; 26use Getopt::Long; 27use Text::Wrap; 28 29use Amanda::Device qw( :constants ); 30use Amanda::Debug qw( :logging ); 31use Amanda::Config qw( :init :getconf config_dir_relative ); 32use Amanda::Util qw( :constants ); 33use Amanda::Changer; 34use Amanda::Header qw( :constants ); 35use Amanda::MainLoop; 36use Amanda::Tapelist; 37 38my $exit_status = 0; 39 40## 41# Subcommand handling 42 43my %subcommands; 44 45sub usage { 46 print STDERR "Usage: amlabel [--barcode <barcode>] [--meta <meta>] [--assign] [--version]\n" 47 . " [-f] [-o configoption]* <conf> [<label>] [slot <slot-number>]\n"; 48 exit(1); 49} 50 51Amanda::Util::setup_application("amlabel", "server", $CONTEXT_CMDLINE); 52 53my $config_overrides = new_config_overrides($#ARGV+1); 54my ($opt_force, $opt_config, $opt_slot, $opt_label); 55my ($opt_barcode, $opt_meta, $opt_assign); 56 57$opt_force = 0; 58$opt_barcode = undef; 59$opt_meta = undef; 60$opt_assign = undef; 61 62debug("Arguments: " . join(' ', @ARGV)); 63Getopt::Long::Configure(qw(bundling)); 64GetOptions( 65 'version' => \&Amanda::Util::version_opt, 66 'help|usage|?' => \&usage, 67 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); }, 68 'f' => \$opt_force, 69 'barcode=s' => \$opt_barcode, 70 'meta=s' => \$opt_meta, 71 'assign' => \$opt_assign, 72 'version' => \&Amanda::Util::version_opt, 73) or usage(); 74 75if ($opt_assign && (!$opt_meta and !$opt_barcode)) { 76 print STDERR "--assign require --barcode or --meta\n"; 77 usage(); 78} 79 80usage() if @ARGV == 0; 81$opt_config = $ARGV[0]; 82if (@ARGV == 1) { 83 $opt_slot = undef; 84 $opt_label = undef; 85} elsif (@ARGV == 2) { 86 $opt_slot = undef; 87 $opt_label = $ARGV[1]; 88} elsif (@ARGV == 3 and $ARGV[1] eq 'slot') { 89 $opt_slot = $ARGV[2]; 90 $opt_label = undef; 91} elsif (@ARGV == 4 and $ARGV[2] eq 'slot') { 92 $opt_slot = $ARGV[3]; 93 $opt_label = $ARGV[1]; 94} else { 95 usage(); 96} 97 98set_config_overrides($config_overrides); 99config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config); 100my ($cfgerr_level, @cfgerr_errors) = config_errors(); 101if ($cfgerr_level >= $CFGERR_WARNINGS) { 102 config_print_errors(); 103 if ($cfgerr_level >= $CFGERR_ERRORS) { 104 print STDERR "errors processing config file"; 105 exit 1; 106 } 107} 108 109Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER); 110 111my ($tlf, $tl, $res); 112 113sub failure { 114 my ($msg, $finished_cb) = @_; 115 print STDERR "$msg\n"; 116 $exit_status = 1; 117 if ($res) { 118 $res->release(finished_cb => sub { 119 # ignore error 120 $finished_cb->() 121 }); 122 } else { 123 $finished_cb->(); 124 } 125} 126 127sub main { 128 my ($finished_cb) = @_; 129 my $gerr; 130 my $chg; 131 my $dev; 132 my $dev_ok; 133 134 my $steps = define_steps 135 cb_ref => \$finished_cb, 136 finalize => sub { $chg->quit() if defined $chg }; 137 138 step start => sub { 139 my $labelstr = getconf($CNF_LABELSTR); 140 if (defined ($opt_label) && $opt_label !~ /$labelstr/) { 141 return failure("Label '$opt_label' doesn't match labelstr '$labelstr'.", $finished_cb); 142 } 143 144 $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST)); 145 $tl = Amanda::Tapelist->new($tlf); 146 if (!defined $tl) { 147 return failure("Can't load tapelist file ($tlf)", $finished_cb); 148 } 149 150 $chg = Amanda::Changer->new(undef, tapelist => $tl); 151 152 return failure($chg, $finished_cb) 153 if $chg->isa("Amanda::Changer::Error"); 154 155 if ($opt_assign) { 156 return $steps->{'assign'}->(); 157 } 158 159 if (defined($opt_label) && !$opt_force) { 160 if ($tl->lookup_tapelabel($opt_label)) { 161 return failure("Label '$opt_label' already on a volume", $finished_cb); 162 } 163 } 164 165 $steps->{'load'}->(); 166 }; 167 168 step load => sub { 169 print "Reading label...\n"; 170 if ($opt_slot) { 171 $chg->load(slot => $opt_slot, mode => "write", 172 res_cb => $steps->{'loaded'}); 173 } elsif ($opt_barcode) { 174 $chg->inventory(inventory_cb => $steps->{'inventory'}); 175 } else { 176 $chg->load(relative_slot => "current", mode => "write", 177 res_cb => $steps->{'loaded'}); 178 } 179 }; 180 181 step inventory => sub { 182 my ($err, $inv) = @_; 183 184 return failure($err, $finished_cb) if $err; 185 186 for my $sl (@$inv) { 187 if ($sl->{'barcode'} eq $opt_barcode) { 188 return $chg->load(slot => $sl->{'slot'}, mode => "write", 189 res_cb => $steps->{'loaded'}); 190 } 191 } 192 193 return failure("No volume with barcode '$opt_barcode' available", $finished_cb); 194 }; 195 196 step loaded => sub { 197 (my $err, $res) = @_; 198 199 return failure($err, $finished_cb) if $err; 200 201 if (defined $opt_slot && defined $opt_barcode && 202 $opt_barcode ne $res->{'barcode'}) { 203 if (defined $res->{'barcode'}) { 204 return failure("Volume in slot $opt_slot have barcode '$res->{'barcode'}, it is not '$opt_barcode'", $finished_cb); 205 } else { 206 return failure("Volume in slot $opt_slot have no barcode", $finished_cb); 207 } 208 } 209 $dev = $res->{'device'}; 210 $dev_ok = 1; 211 if ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED) { 212 if (!$dev->volume_header or $dev->volume_header->{'type'} == $F_EMPTY) { 213 print "Found an empty tape.\n"; 214 } else { 215 # force is required for non-Amanda tapes 216 print "Found a non-Amanda tape.\n"; 217 $dev_ok = 0 unless ($opt_force); 218 } 219 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) { 220 # it's OK to force through VOLUME_ERROR 221 print "Error reading volume label: " . $dev->error_or_status(), "\n"; 222 $dev_ok = 0 unless ($opt_force); 223 } elsif ($dev->status != $DEVICE_STATUS_SUCCESS) { 224 # but anything else is fatal 225 print "Error reading volume label: " . $dev->error_or_status(), "\n"; 226 $dev_ok = 0; 227 } else { 228 # this is a labeled Amanda tape 229 my $label = $dev->volume_label; 230 my $labelstr = getconf($CNF_LABELSTR); 231 232 if ($label !~ /$labelstr/) { 233 print "Found label '$label', but it is not from configuration " . 234 "'" . Amanda::Config::get_config_name() . "'.\n"; 235 $dev_ok = 0 unless ($opt_force); 236 } elsif ($tl->lookup_tapelabel($label)) { 237 print "Volume with label '$label' is active and contains data from this configuration.\n"; 238 if ($opt_force) { 239 # if -f, then the user should clean things up.. 240 print "Consider using 'amrmtape' to remove volume '$label' from the catalog.\n"; 241 # note that we don't run amrmtape automatically, as it could result in data loss when 242 # multiple volumes have (perhaps accidentally) the same label 243 } else { 244 $dev_ok = 0 245 } 246 } else { 247 print "Found Amanda volume '$label'.\n"; 248 } 249 } 250 251 $res->get_meta_label(finished_cb => $steps->{'got_meta'}); 252 }; 253 254 step got_meta => sub { 255 my ($err, $meta) = @_; 256 257 if (defined $meta && defined $opt_meta && $meta ne $opt_meta) { 258 return failure("Device meta '$meta' is not the same as the --meta argument '$opt_meta'", $finished_cb); 259 } 260 $meta = $opt_meta if !defined $meta; 261 ($meta, my $merr) = $res->make_new_meta_label() if !defined $meta; 262 if (defined $merr) { 263 return failure($merr, $finished_cb); 264 } 265 $opt_meta = $meta; 266 267 my $label = $opt_label; 268 if (!defined($label)) { 269 ($label, my $lerr) = $res->make_new_tape_label(meta => $meta); 270 if (defined $lerr) { 271 return failure($lerr, $finished_cb); 272 } 273 } 274 275 if ($dev_ok) { 276 print "Writing label '$label'...\n"; 277 278 if (!$dev->start($ACCESS_WRITE, $label, "X")) { 279 return failure("Error writing label: " . $dev->error_or_status(), $finished_cb); 280 } elsif (!$dev->finish()) { 281 return failure("Error finishing device: " . $dev->error_or_status(), $finished_cb); 282 } 283 284 print "Checking label...\n"; 285 my $status = $dev->read_label(); 286 if ($status != $DEVICE_STATUS_SUCCESS) { 287 return failure("Checking the tape label failed: " . $dev->error_or_status(), 288 $finished_cb); 289 } elsif (!$dev->volume_label) { 290 return failure("No label found.", $finished_cb); 291 } elsif ($dev->volume_label ne $label) { 292 my $got = $dev->volume_label; 293 return failure("Read back a different label: got '$got', but expected '$label'", 294 $finished_cb); 295 } elsif ($dev->volume_time ne "X") { 296 my $got = $dev->volume_time; 297 return failure("Read back a different timetstamp: got '$got', but expected 'X'", 298 $finished_cb); 299 } 300 301 # update the tapelist 302 $tl->reload(1); 303 $tl->remove_tapelabel($label); 304 $tl->add_tapelabel("0", $label, undef, 1, $meta, $res->{'barcode'}, $dev->block_size/1024); 305 $tl->write(); 306 307 print "Success!\n"; 308 309 # notify the changer 310 $res->set_label(label => $label, finished_cb => $steps->{'set_meta_label'}); 311 } else { 312 return failure("Not writing label.", $finished_cb); 313 } 314 }; 315 316 step set_meta_label => sub { 317 my ($gerr) = @_; 318 319 if ($opt_meta) { 320 return $res->set_meta_label(meta => $opt_meta, 321 finished_cb => $steps->{'labeled'}); 322 } else { 323 return $steps->{'labeled'}->(); 324 } 325 }; 326 327 step labeled => sub { 328 my ($err) = @_; 329 $gerr = $err if !$gerr; 330 331 $res->release(finished_cb => $steps->{'released'}); 332 }; 333 334 step released => sub { 335 my ($err) = @_; 336 return failure($gerr, $finished_cb) if $gerr; 337 return failure($err, $finished_cb) if $err; 338 339 $finished_cb->(); 340 }; 341 342 step assign => sub { 343 my $tle; 344 $tle = $tl->lookup_tapelabel($opt_label); 345 if (defined $tle) { 346 my $meta = $opt_meta; 347 if (defined $meta) { 348 if (defined($tle->{'meta'}) && $meta ne $tle->{'meta'} && 349 !$opt_force) { 350 return failure("Can't change meta-label with --force, old meta-label is '$tle->{'meta'}'", $finished_cb); 351 } 352 } else { 353 $meta = $tle->{'meta'}; 354 } 355 my $barcode = $opt_barcode; 356 if (defined $barcode) { 357 if (defined($tle->{'barcode'}) && 358 $barcode ne $tle->{'barcode'} && 359 !$opt_force) { 360 return failure("Can't change barcode with --force, old barcode is '$tle->{'barcode'}'", $finished_cb); 361 } 362 } else { 363 $barcode = $tle->{'barcode'}; 364 } 365 366 $tl->reload(1); 367 $tl->remove_tapelabel($opt_label); 368 $tl->add_tapelabel($tle->{'datestamp'}, $tle->{'label'}, 369 $tle->{'comment'}, $tle->{'reuse'}, $meta, 370 $barcode); 371 $tl->write(); 372 } else { 373 return failure("Label '$opt_label' is not in the tapelist file", $finished_cb); 374 } 375 376 $chg->inventory(inventory_cb => $steps->{'assign_inventory'}); 377 }; 378 379 step assign_inventory => sub { 380 my ($err, $inv) = @_; 381 382 if ($err) { 383 return $finished_cb->() if $err->notimpl; 384 return failure($err, $finished_cb); 385 } 386 387 for my $sl (@$inv) { 388 if (defined $sl->{'label'} && $sl->{'label'} eq $opt_label) { 389 return $chg->set_meta_label(meta => $opt_meta, 390 slot => $sl->{'slot'}, 391 finished_cb => $steps->{'done'}); 392 } 393 } 394 $finished_cb->(); 395 }; 396 397 step done => sub { 398 $finished_cb->(); 399 } 400} 401 402main(\&Amanda::MainLoop::quit); 403Amanda::MainLoop::run(); 404Amanda::Util::finish_application(); 405exit($exit_status); 406