Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Changer.pm
1 # Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
18
19 package Amanda::Changer;
20
21 use strict;
22 use warnings;
23 use Carp qw( confess cluck );
24 use POSIX ();
25 use Fcntl qw( O_RDWR O_CREAT LOCK_EX LOCK_NB );
26 use Data::Dumper;
27 use vars qw( @ISA );
28
29 use Amanda::Paths;
30 use Amanda::Util;
31 use Amanda::Config qw( :getconf );
32 use Amanda::Device qw( :constants );
33 use Amanda::Debug qw( debug );
34 use Amanda::MainLoop;
35
36 =head1 NAME
37
38 Amanda::Changer -- interface to changer scripts
39
40 =head1 SYNOPSIS
41
42     use Amanda::Changer;
43
44     my $chg = Amanda::Changer->new(); # loads the default changer; OR
45     $chg = Amanda::Changer->new("somechanger"); # references a defined changer in amanda.conf
46
47     $chg->load(
48         label => "TAPE-012",
49         mode => "write",
50         res_cb => sub {
51             my ($err, $reservation) = @_;
52             if ($err) {
53                 die $err->{message};
54             }
55             $dev = $reservation->{'device'};
56             # use device..
57         });
58
59     # later..
60     $reservation->release(finished_cb => $start_next_volume);
61
62     # later..
63     $chg->quit();
64
65 =head1 INTERFACE
66
67 All operations in the module return immediately, and take as an argument a
68 callback function which will indicate completion of the changer operation -- a
69 kind of continuation.  The caller should run a main loop (see
70 L<Amanda::MainLoop>) to allow the interactions with the changer script to
71 continue.
72
73 A new object is created with the C<new> function as follows:
74
75   my $chg = Amanda::Changer->new($changer_name,
76                                  tapelist       => $tapelist,
77                                  labelstr       => $labelstr,
78                                  autolabel      => $autolabel,
79                                  meta_autolabel => $meta_autolabel);
80
81 to create a named changer (a name provided by the user, either specifying a
82 changer directly or specifying a changer definition), or
83
84   my $chg = Amanda::Changer->new(undef,
85                                  tapelist       => $tapelist,
86                                  labelstr       => $labelstr,
87                                  autolabel      => $autolabel,
88                                  meta_autolabel => $meta_autolabel);
89
90 to run the default changer.  This function handles the many ways a user can
91 configure a changer.
92
93 If there is a problem creating the new object, then the resulting object will
94 be a fatal C<Error> object (described below).  Thus the usual recipe for
95 creating a new changer is
96
97   my $chg = Amanda::Changer->new($changer_name);
98   if ($chg->isa("Amanda::Changer::Error")) {
99     die("Error creating changer $changer_name: $chg");
100   }
101
102 C<tapelist> must be an Amanda::Tapelist object. It is required if you want to
103 use $chg->volume_is_labelable(), $chg->make_new_tape_label(),
104 $chg->make_new_meta_label(), $res->make_new_tape_label() or
105 $res->make_new_meta_label().
106 C<labelstr> must be like getconf($CNF_LABELSTR), that value is used if C<labelstr> is not set.
107 C<autolabel> must be like getconf($CNF_AUTOLABEL), that value is used if C<autolabel> is not set.
108 C<meta_autolabel> must be like getconf($CNF_META_AUTOLABEL), that value is used if C<meta_autolabel> is not set.
109 =head2 MEMBER VARIABLES
110
111 Note that these variables are not set until after the subclass constructor is
112 finished.
113
114 =over 4
115
116 =item C<< $chg->{'chg_name'} >>
117
118 Gives the name of the changer.  This name will make sense to the user, but will
119 not necessarily form a valid changer specification.  It should be used to
120 describe the changer in messages to the user.
121
122 =back
123
124 =head2 CALLBACKS
125
126 All changer callbacks take an error object as the first parameter.  If no error
127 occurred, then this parameter is C<undef> and the remaining parameters are
128 defined.
129
130 A res_cb C<$cb> is called back as:
131
132  $cb->($error, undef);
133
134 in the event of an error, or
135
136  $cb->(undef, $reservation);
137
138 with a successful reservation. res_cb must always be specified.  A finished_cb
139 C<$cb> is called back as
140
141  $cb->($error);
142
143 in the event of an error, or
144
145  $cb->(undef);
146
147 on success. A finished_cb may be omitted if no notification of completion is
148 required.
149
150 Other callback types are defined below.
151
152 =head2 ERRORS
153
154 When a callback is made with an error, it is an object of type
155 C<Amanda::Changer::Error>.  When interpolated into a string, this object turns
156 into a simple error message.  However, it has some additional methods that can
157 be used to determine how to respond to the error.  First, the error message is
158 available explicitly as C<< $err->message >>.  The error type is available as
159 C<< $err->{'type'} >>, although checks for particular error types should use
160 the C<TYPE> methods instead, as perl is better able to detect typos with this
161 syntax:
162
163   if ($err->failed) { ... }
164
165 The error types are:
166
167   fatal      Changer is no longer useable
168   failed     Operation failed, but the changer is OK
169
170 The API may add other error types in the future (for example, to indicate
171 that a required resource is already reserved).
172
173 Errors of the type C<fatal> indicate that the changer should not be used any
174 longer, and in most cases the caller should terminate abnormally.  For example,
175 configuration or hardware errors are generally fatal.
176
177 If an operation fails, but the changer remains viable, then the error type is
178 C<failed>.  The reason for the failure is usually clear to the user from the
179 message, but for callers who may need to distinguish, C<< $err->{'reason'} >>
180 has one of the following values:
181
182   notfound          The requested volume was not found
183   invalid           The caller's request was invalid (e.g., bad slot)
184   notimpl           The requested operation is not supported
185   volinuse          The requested volume or slot is already in use
186   driveinuse        All drives are in use
187   unknown           Unknown reason
188   empty             The slot is empty
189
190 Like types, checks for particular reasons should use the methods, to avoid
191 undetected typos:
192
193   if ($err->failed and $err->notimpl) { ... }
194
195 Other reasons may be added in the future, so a caller should check for the
196 reasons it expects, and treat any other failures as of unknown cause.
197
198 When the desired slot cannot be loaded because it is already in use, the
199 C<volinuse> error comes with an extra parameter, C<slot>, giving the slot in
200 question.  This parameter is not defined for other cases.
201
202 =head2 CURRENT SLOT
203
204 Changers maintain a global concept of a "current" slot, for compatibility with
205 Amanda algorithms such as the taperscan.  However, it is not compatible with
206 concurrent use of the same changer, and may be inefficient for some changers,
207 so new algorithms should avoid using it, preferring instead to load the correct
208 tape immediately (with C<load>), and to progress from tape to tape using the
209 C<relative_slot> parameter to C<load>.
210
211 =head2 CHANGER OBJECTS
212
213 =head3 quit
214
215 To terminate a changer object.
216
217 =head3 load
218
219 The most common operation with a tape changer is to load a volume.  The C<load>
220 method is heavily overloaded to support a number of different ways to specify a
221 volume.
222
223 In general, the method takes a C<res_cb> giving a callback that will receive
224 the reservation.  If set_current is specified and true, then the changer's
225 current slot should be updated to correspond to C<$slot>. If not, then the changer
226 should not update its current slot (but some changers will anyway -
227 specifically, chg-compat).
228
229 The load method always read the label if it succeed to load a volume.
230
231 The optional C<mode> describes the intended use of the volume by the caller,
232 and should be one of C<"read"> (the default) or C<"write">.  Changers managing
233 WORM media may use this parameter to provide a fresh volume for writing, but to
234 search for already-written volumes when reading.
235
236 The load method has a number of permutations:
237
238   $chg->load(res_cb => $cb,
239              label => $label,
240              mode => $mode,
241              set_current => $sc)
242
243 Load and reserve a volume with the given label. This may leverage any barcodes
244 or other indices that the changer has available.
245
246 Note that the changer I<tries> to load the requested volume, but it's a mean
247 world out there, and you may not get what you want, so check the label on the
248 loaded volume before getting started.
249
250   $chg->load(res_cb => $cb,
251              slot => $slot,
252              mode => $mode,
253              set_current => $sc)
254
255 Load and reserve the volume in the given slot. C<$slot> is a string specifying the slot
256 to load, provided by the user or from some other invocation of this changer.
257 Note that slots are not necessarily numeric, so performing arithmetic on this
258 value is an error.
259
260 If the slot does not exist, C<res_cb> will be called with a C<notfound> error.
261 Empty slots are considered empty.
262
263   $chg->load(res_cb => $cb,
264              relative_slot => "current",
265              mode => $mode)
266
267 Reserve the volume in the "current" slot. This is used by the traditional
268 taperscan algorithm to begin its search.
269
270   $chg->load(res_cb => $cb,
271              relative_slot => "next",
272              slot => $slot,
273              except_slots => { %except_slots },
274              mode => $mode,
275              set_current => $sc)
276
277 Reserve the volume that follows the given slot or, if C<slot> is omitted, the
278 volume that follows the current slot.  This will skip empty slots as if they
279 were not present in the changer.
280
281 The optional C<except_slots> argument specifies a hash of slots that should
282 I<not> be loaded.  Keys are slot names, and the hash values are ignored.  This
283 is useful as a termination condition when scanning all of the slots in a
284 changer: keep a hash of all slots already loaded, and pass that hash in
285 C<except_slots>.  When the load operation returns a C<notfound> error, the scan
286 is complete.
287
288 =head3 info
289
290   $chg->info(info_cb => $cb,
291              info => [ $key1, $key2, .. ])
292
293 Query the changer for miscellaneous information.  Any number of keys may be
294 specified.  The C<info_cb> is called with C<$error> as the first argument,
295 much like a C<res_cb>, but the remaining arguments form a hash giving values
296 for all of the requested keys that are supported by the changer.  The preamble
297 to such a callback is usually
298
299   info_cb => sub {
300     my ($error, %results) = @_;
301     # ..
302   }
303
304 Supported keys are:
305
306 =over 2
307
308 =item num_slots
309
310 The total number of slots in the changer device.  If this key is not present or
311 -1, then the device cannot determine its slot count (for example, an archival
312 device that names slots by timestamp could potentially run until the heat-death
313 of the universe).
314
315 =item vendor_string
316
317 A string describing the name and model of the changer device.
318
319 =item fast_search
320
321 If true, then this changer implements searching (loading by label) with
322 something more efficient than a sequential scan through the volumes.  This
323 information affects some taperscan algorithms and recovery programs, which may
324 choose to do their own manual scan instead of invoking many potentially slow
325 searches.
326
327 =back
328
329 =head3 reset
330
331   $chg->reset(finished_cb => $cb)
332
333 Reset the changer to a "base" state. This will generally reset the "current"
334 slot to something the user would think of as the "first" tape, unload any
335 loaded drives, etc. It is an error to call this while any reservations are
336 outstanding.
337
338 =head3 clean
339
340   $chg->clean(finished_cb => $cb,
341               drive => $drivename)
342
343 Clean a drive, if the changer supports it. Drivename can be omitted for devices
344 with only one drive, or can be an arbitrary string from the user (e.g., an
345 amtape argument). Note that some changers cannot detect the completion of a
346 cleaning cycle; in this case, the user will just need to delay further Amanda
347 activities until the cleaning is complete.
348
349 =head3 eject
350
351   $chg->eject(finished_cb => $cb,
352               drive => $drivename)
353
354 Eject the volume in a drive, if the changer supports it.  Drivename is as
355 specified to C<clean>.  If possible, applications should prefer to eject a
356 reserved volume when finished with it (C<< $res->release(eject => 1) >>), to
357 ensure that the correct volume is ejected from a multi-drive changer.
358
359 =head3 update
360
361   $chg->update(finished_cb => $cb,
362                user_msg_fn => $fn,
363                changed => $changed)
364
365 The user has changed something -- loading or unloading tapes, reconfiguring the
366 changer, etc. -- that may have invalidated the database.  C<$changed> is a
367 changer-specific string indicating what has changed; if it is omitted, the
368 changer will check everything.
369
370 Since updates can take a long time, and users often want to know what's going
371 on, the update method will call C<user_msg_fn>, if specified, with
372 user-oriented messages appropriate to the changer.
373
374 =head3 inventory
375
376   $chg->inventory(inventory_cb => $cb)
377
378 The C<inventory_cb> is called with an error object as the first parameter, or
379 C<undef> if no error occurs.  The second parameter is an arrayref containing an
380 ordered list of information about the slots in the changer. The order never
381 change, but some entries can be added or removed.
382
383 Each slot is represented by a hash with the following keys:
384
385 =head3 make_new_tape_label
386
387   $chg->make_new_tape_label(barcode => $barcode,
388                             slot    => $slot,
389                             meta    => $meta);
390
391 To devise a new name for a volume using the C<barcode> and C<meta> arguments.
392 This will return C<undef> if no label could be created.
393
394 =head3 make_new_meta_label
395
396   $chg->make_new_meta_label();
397
398 To devise a new meta name for a meta volume.
399 This will return C<undef> if no label could be created.
400
401 =head3 have_inventory
402
403   $chg->have_inventory() 
404
405 Return True if the changer have the inventory method.
406
407 =head3 volume_is_labelable
408
409   $chg->volume_is_labelable($device_status, $f_type, $label);
410
411 Return 1 if the volume is labelable acording to the autolabel setting.
412
413 =over 4
414
415 =item slot
416
417 The slot name
418
419 =item current
420
421 Set to C<1> if it is the current slot.
422
423 =item state
424
425 Set to C<SLOT_FULL> if the slot is full, C<SLOT_EMPTY> if the slot is empty (no
426 volume in slot), C<SLOT_UNKNOWN> if the changer doesn't know if the slot is full
427 or not (but it can know), or undef if the changer can't know if the slot is full or not.
428 A changer that doesn't keep state must set it to undef, like chg-single.
429 These constants are available in the C<:constants> export tag.
430
431 A blank or erased volume is not the same as an empty slot.
432
433 =item device_status
434
435 The device status after the open or read_label, undef if device status is unknown.
436
437 =item f_type
438
439 The file header type as returned by read_label, only if device_status is DEVICE_STATUS_SUCCESS.
440
441 =item label
442
443 The label on the volume in this slot, can be set by barcode or by read_label if f_type is Amanda::Header::F_TAPESTART.
444
445 =item barcode (optional)
446
447 The barcode for the volume in this slot, if barcodes are available.
448
449 =item reserved
450
451 Set to C<1> if this slot is reserved, either by this process or another
452 process.  This is only set for I<exclusive> reservations, meaning that loading
453 the slot would result in an C<volinuse> error.  Devices which can support
454 concurrent access will never set this flag.
455
456 =item loaded_in (optional)
457
458 For changers which have distinct user-visible drives, this gives the drive
459 currently accessing the volume in this slot.
460
461 =item import_export (optional)
462
463 Set to C<1> if this is an import-export slot -- a slot in which the user can
464 easily add or remove volumes.  This information may be useful for operations to
465 bulk-import newly-inserted tapes or bulk-export a set of tapes.
466
467 =back
468
469 =head3 move
470
471   $chg->move(finished_cb => $cb,
472              from_slot => $from,
473              to_slot => $to)
474
475 Move a volume between two slots in the changer. These slots are provided by the
476 user, and have meaning for the changer.
477
478 =head2 RESERVATION OBJECTS
479
480 =head3 Methods
481
482 =head3 $res->{'chg'}
483
484 This is the changer object.
485
486 =head3 $res->{'device'}
487
488 This is the fully configured device for the reserved volume.  The device is not
489 started.
490
491 =head3 $res->{'this_slot'}
492
493 This is the name of this slot.  It is an arbitrary string which will
494 have some meaning to the changer's C<load()> method. It is safe to
495 access this field after the reservation has been released.
496
497 =head3 $res->{'barcode'}
498
499 If this changer supports barcodes, then this is the barcode of the reserved
500 volume.  This can be helpful for labeling tapes using their barcode.
501
502 =head3 $label = $res->make_new_tape_label()
503
504 To devise a new name for a volume.
505 This will return C<undef> if no label could be created.
506
507 =head3 $meta = $res->make_new_meta_label()
508
509 To devise a new meta name for a meta volume.
510 This will return C<undef> if no label could be created.
511
512 =head3 $res->release(finished_cb => $cb, eject => $eject)
513
514 This is how an Amanda application indicates that it no longer needs the
515 reserved volume. The callback is called after any related operations are
516 complete -- possibly immediately. Some drives and changers have a notion of
517 "ejecting" a volume, and some don't. In particular, a manual changer can cause
518 the tape drive to eject the tape, while a tape robot can move a tape back to
519 storage, leaving the drive empty. If the eject parameter is given and true, it
520 indicates that Amanda is done with the volume and has reason to believe the
521 user is done with the volume, too -- for example, when a tape has been written
522 completely.
523
524 A reservation will be released automatically when the object is destroyed, but
525 in this case no finished_cb is given, so the release operation may not complete
526 before the process exits. Wherever possible, reservations should be explicitly
527 released.
528
529 =head3 $res->set_label(finished_cb => $cb, label => $label)
530
531 This is how Amanda indicates to the changer that the volume in the device has
532 been (re-)labeled. Changers can keep a database of volume labels by slot or by
533 barcode, or just ignore this function and call $cb immediately. Note that the
534 reservation must still be held when this function is called.
535
536 =head1 SUBCLASS HELPERS
537
538 C<Amanda::Changer> implements some methods and attributes to help subclass
539 implementers.
540
541 =head2 INFO
542
543 Implementing the C<info> method can be tricky, because it can potentially request
544 a number of keys that require asynchronous access.  The C<info> implementation in
545 this class may make the process a bit easier.
546
547 First, if the method C<info_setup> is defined, C<info> calls it, passing it a
548 C<finished_cb> and the list of desired keys, C<info>.  This method is useful to
549 gather information that is useful for several info keys.
550
551 Next, for each requested key, C<info> calls
552
553   $self->info_key($key, %params)
554
555 including a regular C<info_cb> callback.  The C<info> method will wait for
556 all C<info_key> invocations to finish, then collect the results or errors that
557 occur.
558
559 =head2 ERROR HANDLING
560
561 To create a new error object, use C<< $self->make_error($type, $cb, %args) >>.
562 This method will create a new C<Amanda::Changer::Error> object and optionally
563 invoke a callback with it.  If C<$type> is C<fatal>, then
564 C<< $chg->{'fatal_error'} >> is made a reference to the new error object.  The
565 callback C<$cb> (which should be made using C<make_cb()> from
566 C<Amanda::MainLoop>) is called with the new error object.  The C<%args> are
567 added to the new error object.  In use, this looks something like:
568
569   if (!$success) {
570     return $self->make_error("failed", $params{'res_cb'},
571             reason => "notfound",
572             message => "Volume '$label' not found");
573   }
574
575 This method can also be called as a class method, e.g., from a constructor.
576 In this case, it returns the resulting error object, which should be fatal.
577
578   if (!$config_ok) {
579     return Amanda::Changer->make_error("fatal", undef,
580             message => "config error");
581   }
582
583 For cases where a number of errors have occurred, it is helpful to make a
584 "combined" error.  The method C<make_combined_error> takes care of this
585 operation, given a callback and an array of tuples C<[ $description, $err ]>
586 for each error.  This method uses some heuristics to figure out the
587 appropriate type and reason for the combined error.
588
589   if ($left_err and $right_err) {
590     return $self->make_combined_error($params{'finished_cb'},
591         [ [ "from the left", $left_err ],
592           [ "from the right", $right_err ] ]);
593   }
594
595 Any additional keyword arguments to C<make_combined_error> are put into the
596 combined error; this is useful to set the C<slot> attribute.
597
598 The method C<< $self->check_error($cb) >> is a useful method for subclasses to
599 avoid doing anything after a fatal error.  This method checks
600 C<< $self->{'fatal_error'} >>.  If the error is defined, the method calls C<$cb>
601 and returns true.  The usual recipe is
602
603   sub load {
604     my $self = shift;
605     my %params = @_;
606
607     return if $self->check_error($params{'res_cb'});
608     # ...
609   }
610
611 =head2 CONFIG
612
613 C<Amanda::Changer->new> calls subclass constructors with two parameters: a
614 configuration object and a changer specification.  The changer specification is
615 the string that led to creation of this changer device.  The configuration
616 object is of type C<Amanda::Changer::Config>, and can be treated as a hashref
617 with the following keys:
618
619   name                  -- name of the changer section (or "default")
620   is_global             -- true if this changer is the default changer
621   tapedev               -- tapedev parameter
622   tpchanger             -- tpchanger parameter
623   changerdev            -- changerdev parameter
624   changerfile           -- changerfile parameter
625   properties            -- all properties for this changer
626   device_properties     -- device properties from this changer
627
628 The four parameters are just as supplied by the user, either in the global
629 config or in a changer section.  Changer authors are cautioned not to try to
630 override any of these parameters as previous changers have done (e.g.,
631 C<changerfile> specifying both configuration and state files).  Use properties
632 instead.
633
634 The C<properties> and C<device_properties> parameters are in the format
635 provided by C<Amanda::Config>.  If C<is_global> is true, then
636 C<device_properties> will include any device properties specified globally, as
637 well as properties culled from the global tapetype.
638
639 The C<configure_device> method generally takes care of the intricacies of
640 handling device properties.  Pass it a newly opened device and it will apply
641 the relevant properties, returning undef on success or an error message on
642 failure.
643
644 The C<get_property> method is a shortcut method to get the value of a changer
645 property, ignoring its the priority and other attributes.  In a list context,
646 it returns all values for the property; in a scalar context, it returns the
647 first value specified.
648
649 Many properties are boolean, and Amanda has a habit of accepting a number of
650 different ways of writing boolean values.  The method
651 C<< $config->get_boolean_property($prop, $default) >> will parse such a
652 property, returning 0 or 1 if the property is specified, C<$default> if it is
653 not specified, or C<undef> if the property cannot be parsed.
654
655 =head2 PERSISTENT STATE AND LOCKING
656
657 Many changer subclasses need to track state across invocations and between
658 different processes, and to ensure that the state is read and written
659 atomically.  The C<with_locked_state> provides this functionality by
660 locking a statefile, only unlocking it after any changes have been written back
661 to it.  Subclasses can use this method both for mutual exclusion (ensuring that
662 only one changer operation is in progress at any time) and for atomic state
663 storage.
664
665 The C<with_locked_state> method works like C<synchronized> (in
666 L<Amanda::MainLoop>), but with some extra arguments:
667
668   $self->with_locked_state($filename, $some_cb, sub {
669     # note: $some_cb shadows outer $some_cb; see Amanda::MainLoop::synchronized
670     my ($state, $some_cb) = @_;
671     # ... and eventually:
672     $some_cb->(...);
673   });
674
675 The callback C<$some_cb> is assumed to take a changer error as its first
676 argument, and if there are any errors locking the statefile, they will be
677 reported directly to this callback.  Otherwise, a wrapped version of
678 C<$some_cb> is passed to the inner C<sub>.  When this wrapper is invoked, the
679 state will be written to disk and unlocked before the original callback is
680 invoked.
681
682 The state itself begins as an empty hashref, but subclasses can add arbitrary
683 keys to the hash.  Serialization is currently handled with L<Data::Dumper>.
684
685 =head2 PARAMETER VALIDATION
686
687 The C<validate_params> method is useful to make sure that the proper parameters
688 are present for a particular method, dying if not.  Call it like this:
689
690   $self->validate_params("load", \%params);
691
692 The method currently only supports the "load" method, but can be expanded to
693 cover other methods.
694
695 =head1 SEE ALSO
696
697 The Amanda Wiki (http://wiki.zmanda.com) has a higher-level description of the
698 changer model implemented by this package.
699
700 See amanda-changers(7) for user-level documentation of the changer implementations.
701
702 =cut
703
704 # constants for the states that slots may be in; note that these states still
705 # apply even if the tape is actually loaded in a drive
706
707 # slot is known to contain a volume
708 use constant SLOT_FULL => 1;
709
710 # slot is known to contain no volume
711 use constant SLOT_EMPTY => 2;
712
713 # don't known if slot contains a volume
714 use constant SLOT_UNKNOWN => 3;
715
716 our @EXPORT_OK = qw( SLOT_FULL SLOT_EMPTY SLOT_UNKNOWN );
717 our %EXPORT_TAGS = (
718     constants => [ qw( SLOT_FULL SLOT_EMPTY SLOT_UNKNOWN ) ],
719 );
720
721 # this is a "virtual" constructor which instantiates objects of different
722 # classes based on its argument.  Subclasses should not try to chain up!
723 sub new {
724     shift eq 'Amanda::Changer'
725         or die("Do not call the Amanda::Changer constructor from subclasses");
726     my ($name) = shift;
727     my %params = @_;
728     my ($uri, $cc);
729
730     # creating a named changer is a bit easier
731     if (defined($name)) {
732         # first, is it a changer alias?
733         if (($uri,$cc) = _changer_alias_to_uri($name)) {
734             return _new_from_uri($uri, $cc, $name, %params);
735         }
736
737         # maybe a straight-up changer URI?
738         if (_uri_to_pkgname($name)) {
739             return _new_from_uri($name, undef, $name, %params);
740         }
741
742         # assume it's a device name or alias, and invoke the single-changer
743         return _new_from_uri("chg-single:$name", undef, $name, %params);
744     } else { # !defined($name)
745         if ((getconf_linenum($CNF_TPCHANGER) == -2 ||
746              (getconf_seen($CNF_TPCHANGER) &&
747               getconf_linenum($CNF_TAPEDEV) != -2)) &&
748             getconf($CNF_TPCHANGER) ne '') {
749             my $tpchanger = getconf($CNF_TPCHANGER);
750
751             # first, is it an old changer script?
752             if ($uri = _old_script_to_uri($tpchanger)) {
753                 return _new_from_uri($uri, undef, $tpchanger, %params);
754             }
755
756             # if not, then there had better be no tapdev
757             if (getconf_seen($CNF_TAPEDEV) and getconf($CNF_TAPEDEV) ne '' and
758                 ((getconf_linenum($CNF_TAPEDEV) > 0 and
759                   getconf_linenum($CNF_TPCHANGER) > 0) ||
760                  (getconf_linenum($CNF_TAPEDEV) == -2))) {
761                 return Amanda::Changer::Error->new('fatal',
762                     message => "Cannot specify both 'tapedev' and 'tpchanger' " .
763                         "unless using an old-style changer script");
764             }
765
766             # maybe a changer alias?
767             if (($uri,$cc) = _changer_alias_to_uri($tpchanger)) {
768                 return _new_from_uri($uri, $cc, $tpchanger, %params);
769             }
770
771             # maybe a straight-up changer URI?
772             if (_uri_to_pkgname($tpchanger)) {
773                 return _new_from_uri($tpchanger, undef, $tpchanger, %params);
774             }
775
776             # assume it's a device name or alias, and invoke the single-changer
777             return _new_from_uri("chg-single:$tpchanger", undef, $tpchanger, %params);
778         } elsif (getconf_seen($CNF_TAPEDEV) and getconf($CNF_TAPEDEV) ne '') {
779             my $tapedev = getconf($CNF_TAPEDEV);
780
781             # first, is it a changer alias?
782             if (($uri,$cc) = _changer_alias_to_uri($tapedev)) {
783                 return _new_from_uri($uri, $cc, $tapedev, %params);
784             }
785
786             # maybe a straight-up changer URI?
787             if (_uri_to_pkgname($tapedev)) {
788                 return _new_from_uri($tapedev, undef, $tapedev, %params);
789             }
790
791             # assume it's a device name or alias, and invoke chg-single.
792             # chg-single will check the device immediately and error out
793             # if the device name is invalid.
794             return _new_from_uri("chg-single:$tapedev", undef, $tapedev, %params);
795         } else {
796             return Amanda::Changer::Error->new('fatal',
797                 message => "You must specify one of 'tapedev' or 'tpchanger'");
798         }
799     }
800 }
801
802 sub DESTROY {
803     my $self = shift;
804
805     debug("Changer '$self->{'chg_name'}' not quit") if defined $self->{'chg_name'};
806 }
807
808 # do nothing in quit
809 sub quit {
810     my $self = shift;
811
812     foreach (keys %$self) {
813         delete $self->{$_};
814     }
815 }
816
817 # helper functions for new
818
819 sub _changer_alias_to_uri {
820     my ($name) = @_;
821
822     my $cc = Amanda::Config::lookup_changer_config($name);
823     if ($cc) {
824         my $tpchanger = changer_config_getconf($cc, $CHANGER_CONFIG_TPCHANGER);
825         if ($tpchanger) {
826             if (my $uri = _old_script_to_uri($tpchanger)) {
827                 return ($uri, $cc);
828             }
829         }
830
831         my $seen_tpchanger = changer_config_seen($cc, $CHANGER_CONFIG_TPCHANGER);
832         my $seen_tapedev = changer_config_seen($cc, $CHANGER_CONFIG_TAPEDEV);
833         if ($seen_tpchanger and $seen_tapedev) {
834             return Amanda::Changer::Error->new('fatal',
835                 message => "Cannot specify both 'tapedev' and 'tpchanger' " .
836                     "**unless using an old-style changer script");
837         }
838         if (!$seen_tpchanger and !$seen_tapedev) {
839             return Amanda::Changer::Error->new('fatal',
840                 message => "You must specify one of 'tapedev' or 'tpchanger'");
841         }
842         $tpchanger ||= changer_config_getconf($cc, $CHANGER_CONFIG_TAPEDEV);
843
844         if (_uri_to_pkgname($tpchanger)) {
845             return ($tpchanger, $cc);
846         } else {
847             die "Changer '$name' specifies invalid tpchanger '$tpchanger'";
848         }
849     }
850
851     # not an alias
852     return;
853 }
854
855 sub _old_script_to_uri {
856     my ($name) = @_;
857
858     die("empty changer script name") unless $name;
859
860     if ((-x "$amlibexecdir/$name") or (($name =~ qr{^/}) and (-x $name))) {
861         return "chg-compat:$name"
862     }
863
864     # not an old script
865     return;
866 }
867
868 # try to load the package for the given URI.  $@ is set properly
869 # if this function returns a false value.
870 sub _uri_to_pkgname {
871     my ($name) = @_;
872
873     my ($type) = ($name =~ /^chg-([A-Za-z_]+):/);
874     if (!defined $type) {
875         $@ = "'$name' is not a changer URI";
876         return 0;
877     }
878
879     $type =~ tr/A-Z-/a-z_/;
880
881     # create a package name to see if it's already imported
882     my $pkgname = "Amanda::Changer::$type";
883     my $filename = $pkgname;
884     $filename =~ s|::|/|g;
885     $filename .= '.pm';
886     return $pkgname if (exists $INC{$filename});
887
888     # try loading it
889     eval "use $pkgname;";
890     if ($@) {
891         my $err = $@;
892
893         # determine whether the module doesn't exist at all, or if there was an
894         # error loading it; die if we found a syntax error
895         if (exists $INC{$filename} or $err =~ /did not return a true value/) {
896             die($err);
897         }
898
899         return 0;
900     }
901
902     return $pkgname;
903 }
904
905 sub _new_from_uri { # (note: this sub is patched by the installcheck)
906     my $uri = shift;
907     my $cc = shift;
908     my $name = shift;
909     my %params = @_;
910
911     # as a special case, if the URI came back as an error, just pass
912     # that along.  This lets the _xxx_to_uri methods return errors more
913     # easily
914     if (ref $uri and $uri->isa("Amanda::Changer::Error")) {
915         return $uri;
916     }
917
918     # make up a key for our hash of already-instantiated objects,
919     # using a newline as a separator, since perl can't use tuples
920     # as keys
921     my $uri_cc = "$uri\n";
922     if (defined $cc) {
923         $uri_cc = $uri_cc . changer_config_name($cc);
924     }
925
926     # return a pre-existing changer, if possible
927
928     # look up the type and load the class
929     my $pkgname = _uri_to_pkgname($uri);
930     if (!$pkgname) {
931         die $@;
932     }
933
934     my $rv = eval {$pkgname->new(Amanda::Changer::Config->new($cc), $uri);};
935     die "$pkgname->new return undef" if $@;
936     die "$pkgname->new did not return an Amanda::Changer object or an Amanda::Changer::Error"
937         unless ($rv->isa("Amanda::Changer") or $rv->isa("Amanda::Changer::Error"));
938
939     if ($rv->isa("Amanda::Changer::Error")) {
940         return $rv;
941     }
942
943     if ($rv->isa("Amanda::Changer")) {
944         # add an instance variable or two
945         $rv->{'fatal_error'} = undef;
946     }
947
948     $rv->{'tapelist'} = $params{'tapelist'};
949     $rv->{'autolabel'} = $params{'autolabel'};
950     $rv->{'autolabel'} = getconf($CNF_AUTOLABEL)
951         unless defined $rv->{'autolabel'};
952     $rv->{'labelstr'} = $params{'labelstr'};
953     $rv->{'labelstr'} = getconf($CNF_LABELSTR)
954         unless defined $rv->{'labelstr'};
955     $rv->{'meta_autolabel'} = $params{'meta_autolabel'};
956     $rv->{'meta_autolabel'} = getconf($CNF_META_AUTOLABEL)
957         unless defined $rv->{'meta_autolabel'};
958     $rv->{'chg_name'} = $name;
959     return $rv;
960 }
961
962 # method stubs that return a "notimpl" error
963
964 sub _stubop {
965     my ($op, $cbname, $self, %params) = @_;
966     return if $self->check_error($params{$cbname});
967
968     my $class = ref($self);
969     my $chg_foo = "chg-" . ($class =~ /Amanda::Changer::(.*)/)[0];
970     return $self->make_error("failed", $params{$cbname},
971         reason => "notimpl",
972         message => "'$chg_foo:' does not support $op");
973 }
974
975 sub load { _stubop("loading volumes", "res_cb", @_); }
976 sub reset { _stubop("reset", "finished_cb", @_); }
977 sub clean { _stubop("clean", "finished_cb", @_); }
978 sub eject { _stubop("eject", "finished_cb", @_); }
979 sub update { _stubop("update", "finished_cb", @_); }
980 sub inventory { _stubop("inventory", "inventory_cb", @_); }
981 sub move { _stubop("move", "finished_cb", @_); }
982 sub set_meta_label { _stubop("set_meta_label", "finished_cb", @_); }
983 sub get_meta_label { _stubop("get_meta_label", "finished_cb", @_); }
984
985 sub have_inventory {
986     my $self = shift;
987
988     return $self->can("inventory") ne \&Amanda::Changer::inventory;
989 }
990
991 # info calls out to info_setup and info_key; see POD above
992 sub info {
993     my $self = shift;
994     my %params = @_;
995
996     if (!$self->can('info_key')) {
997         my $class = ref($self);
998         $params{'info_cb'}->("$class does not support info()");
999         return;
1000     }
1001
1002     my ($do_setup, $start_keys, $all_done);
1003
1004     $do_setup = sub {
1005         if ($self->can('info_setup')) {
1006             $self->info_setup(info => $params{'info'},
1007                               finished_cb => sub {
1008                 my ($err) = @_;
1009                 if ($err) {
1010                     $params{'info_cb'}->($err);
1011                 } else {
1012                     $start_keys->();
1013                 }
1014             });
1015         } else {
1016             $start_keys->();
1017         }
1018     };
1019
1020     $start_keys = sub {
1021         my $remaining_keys = 1;
1022         my %key_results;
1023
1024         my $maybe_done = sub {
1025             return if (--$remaining_keys);
1026             $all_done->(%key_results);
1027         };
1028
1029         for my $key (@{$params{'info'}}) {
1030             $remaining_keys++;
1031             $self->info_key($key, info_cb => sub {
1032                 $key_results{$key} = [ @_ ];
1033                 $maybe_done->();
1034             });
1035         }
1036
1037         # we started with $remaining_keys = 1, so decrement it now
1038         $maybe_done->();
1039     };
1040
1041     $all_done = sub {
1042         my %key_results = @_;
1043
1044         # if there are *any* errors, handle them
1045         my @annotated_errs =
1046             map { [ sprintf("While getting info key '%s'", $_), $key_results{$_}->[0] ] }
1047             grep { defined($key_results{$_}->[0]) }
1048             keys %key_results;
1049
1050         if (@annotated_errs) {
1051             return $self->make_combined_error(
1052                 $params{'info_cb'}, [ @annotated_errs ]);
1053         }
1054
1055         # no errors, so combine the results and return them
1056         my %info;
1057         while (my ($key, $result) = each(%key_results)) {
1058             my ($err, %key_info) = @$result;
1059             if (exists $key_info{$key}) {
1060                 $info{$key} = $key_info{$key};
1061             } else {
1062                 warn("No value available for $key");
1063             }
1064         }
1065
1066         $params{'info_cb'}->(undef, %info);
1067     };
1068
1069     $do_setup->();
1070 }
1071
1072 # subclass helpers
1073
1074 sub make_error {
1075     my $self = shift;
1076     my ($type, $cb, %args) = @_;
1077
1078     my $classmeth = $self eq "Amanda::Changer";
1079
1080     if ($classmeth and $type ne 'fatal') {
1081         cluck("type must be fatal when calling make_error as a class method");
1082         $type = 'fatal';
1083     }
1084
1085     my $err = Amanda::Changer::Error->new($type, %args);
1086
1087     if (!$classmeth) {
1088         $self->{'fatal_error'} = $err
1089             if ($err->fatal);
1090
1091         $cb->($err) if $cb;
1092     }
1093
1094     return $err;
1095 }
1096
1097 sub make_combined_error {
1098     my $self = shift;
1099     my ($cb, $suberrors, %extra_args) = @_;
1100     my $err;
1101
1102     if (@$suberrors == 0) {
1103         die("make_combined_error called with no errors");
1104     }
1105
1106     my $classmeth = $self eq "Amanda::Changer";
1107
1108     # if there's only one suberror, just use it directly
1109     if (@$suberrors == 1) {
1110         $err = $suberrors->[0][1];
1111         die("$err is not an Error object")
1112             unless defined($err) and $err->isa("Amanda::Changer::Error");
1113
1114         $err = Amanda::Changer::Error->new(
1115             $err->{'type'},
1116             reason => $err->{'reason'},
1117             message => $suberrors->[0][0] . ": " . $err->{'message'});
1118     } else {
1119         my $fatal = $classmeth or grep { $_->[1]{'fatal'} } @$suberrors;
1120
1121         my $reason;
1122         if (!$fatal) {
1123             my %reasons =
1124                 map { ($_->[1]{'reason'}, undef) }
1125                 grep { $_->[1]{'reason'} }
1126                 @$suberrors;
1127             if ((keys %reasons) == 1) {
1128                 $reason = (keys %reasons)[0];
1129             } else {
1130                 $reason = 'unknown'; # multiple or 0 "source" reasons
1131             }
1132         }
1133
1134         my $message = join("; ",
1135             map { sprintf("%s: %s", @$_) }
1136             @$suberrors);
1137
1138         my %errargs = ( message => $message, %extra_args );
1139         $errargs{'reason'} = $reason unless ($fatal);
1140         $err = Amanda::Changer::Error->new(
1141             $fatal? "fatal" : "failed",
1142             %errargs);
1143     }
1144
1145     if (!$classmeth) {
1146         $self->{'fatal_error'} = $err
1147             if ($err->fatal);
1148
1149         $cb->($err) if $cb;
1150     }
1151
1152     return $err;
1153 }
1154
1155 sub check_error {
1156     my $self = shift;
1157     my ($cb) = @_;
1158
1159     if (defined $self->{'fatal_error'}) {
1160         $cb->($self->{'fatal_error'}) if $cb;
1161         return 1;
1162     }
1163 }
1164
1165 sub lock_statefile {
1166     my $self = shift;
1167     my %params = @_;
1168
1169     my $statefile = $params{'statefile_filename'};
1170     my $lock_cb = $params{'lock_cb'};
1171     Amanda::Changer::StateFile->new($statefile, $lock_cb);
1172 }
1173
1174 sub with_locked_state {
1175     my $self = shift;
1176     my ($statefile, $cb, $sub) = @_;
1177     my ($filelock, $STATE);
1178     my $poll = 0; # first delay will be 0.1s; see below
1179
1180     my $steps = define_steps
1181         cb_ref => \$cb;
1182
1183     step open => sub {
1184         $filelock = Amanda::Util::file_lock->new($statefile);
1185
1186         $steps->{'lock'}->();
1187     };
1188
1189     step lock => sub {
1190         my $rv = $filelock->lock();
1191         if ($rv == 1) {
1192             # loop until we get the lock, increasing $poll to 10s
1193             $poll += 100 unless $poll >= 10000;
1194             return Amanda::MainLoop::call_after($poll, $steps->{'lock'});
1195         } elsif ($rv == -1) {
1196             return $self->make_error("fatal", $cb,
1197                     message => "Error locking '$statefile'");
1198         }
1199
1200         $steps->{'read'}->();
1201     };
1202
1203     step read => sub {
1204         my $contents = $filelock->data();
1205         if ($contents) {
1206             eval $contents;
1207             if ($@) {
1208                 # $fh goes out of scope here, and is thus automatically
1209                 # unlocked
1210                 return $cb->("error reading '$statefile': $@", undef);
1211             }
1212             if (!defined $STATE or ref($STATE) ne 'HASH') {
1213                 return $cb->("'$statefile' did not define \$STATE properly", undef);
1214             }
1215         } else {
1216             # initial state (blank file)
1217             $STATE = {};
1218         }
1219
1220         $sub->($STATE, $steps->{'cb_wrap'});
1221     };
1222
1223     step cb_wrap =>  sub {
1224         my @args = @_;
1225
1226         my $dumper = Data::Dumper->new([ $STATE ], ["STATE"]);
1227         $dumper->Purity(1);
1228         $filelock->write($dumper->Dump);
1229         $filelock->unlock();
1230
1231         # call through to the original callback with the original
1232         # arguments
1233         $cb->(@args);
1234     };
1235 }
1236
1237 sub validate_params {
1238     my ($self, $op, $params) = @_;
1239
1240     if ($op eq 'load') {
1241         unless(exists $params->{'label'} || exists $params->{'slot'} ||
1242                exists $params->{'relative_slot'}) {
1243                 confess "Invalid parameters to 'load'";
1244         }
1245     } else {
1246         confess "don't know how to validate '$op'";
1247     }
1248 }
1249
1250 sub make_new_tape_label {
1251     my $self = shift;
1252     my %params = @_;
1253
1254     my $tl = $self->{'tapelist'};
1255     die ("make_new_tape_label: no tapelist") if !$tl;
1256     if (!defined $self->{'autolabel'}) {
1257         return (undef, "autolabel not set");
1258     }
1259     if (!defined $self->{'autolabel'}->{'template'}) {
1260         return (undef, "template is not set, you must set autolabel");
1261     }
1262     if (!defined $self->{'labelstr'}) {
1263         return (undef, "labelstr not set");
1264     }
1265     my $template = $self->{'autolabel'}->{'template'};
1266     my $labelstr = $self->{'labelstr'};
1267     my $slot_digit = 1;
1268
1269     $template =~ s/\$\$/SUBSTITUTE_DOLLAR/g;
1270     $template =~ s/\$b/SUBSTITUTE_BARCODE/g;
1271     $template =~ s/\$m/SUBSTITUTE_META/g;
1272     $template =~ s/\$o/SUBSTITUTE_ORG/g;
1273     $template =~ s/\$c/SUBSTITUTE_CONFIG/g;
1274     if ($template =~ /\$([0-9]*)s/) {
1275         $slot_digit = $1;
1276         $slot_digit = 1 if $slot_digit < 1;
1277         $template =~ s/\$[0-9]*s/SUBSTITUTE_SLOT/g;
1278     }
1279
1280     my $org = getconf($CNF_ORG);
1281     my $config = Amanda::Config::get_config_name();
1282     my $barcode = $params{'barcode'};
1283     $barcode = '' if !defined $barcode;
1284     my $meta = $params{'meta'};
1285     my $slot = $params{'slot'};
1286     $slot = '' if !defined $slot;
1287     $meta = $self->make_new_meta_label(%params) if !defined $meta;
1288     $meta = '' if !defined $meta;
1289
1290     $template =~ s/SUBSTITUTE_DOLLAR/\$/g;
1291     $template =~ s/SUBSTITUTE_ORG/$org/g;
1292     $template =~ s/SUBSTITUTE_CONFIG/$config/g;
1293     $template =~ s/SUBSTITUTE_META/$meta/g;
1294     # Do not susbtitute the barcode and slot now
1295
1296     (my $npercents =
1297         $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
1298     $npercents = 0 if $npercents eq $template;
1299
1300     my $label;
1301     if ($npercents == 0) {
1302         $label = $template;
1303         $label =~ s/SUBSTITUTE_BARCODE/$barcode/g;
1304         if ($template =~ /SUBSTITUTE_SLOT/) {
1305             my $slot_label = sprintf("%0*d", $slot_digit, $slot);
1306             $label =~ s/SUBSTITUTE_SLOT/$slot_label/g;
1307         }
1308         if ($template =~ /SUBSTITUTE_BARCODE/ && !defined $barcode) {
1309             return (undef, "Can't generate new label because volume has no barcode");
1310         } elsif ($template =~ /SUBSTITUTE_SLOT/ && !defined $slot) {
1311             return (undef, "Can't generate new label because volume has no slot");
1312         } elsif ($label eq $template) {
1313             return (undef, "autolabel require at least one '%'");
1314         } elsif ($tl->lookup_tapelabel($label)) {
1315             return (undef, "Label '$label' already exists");
1316         }
1317     } else {
1318         # make up a sprintf pattern
1319         (my $sprintf_pat =
1320             $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
1321
1322         my %existing_labels;
1323         for my $tle (@{$tl->{'tles'}}) {
1324             if (defined $tle && defined $tle->{'label'}) {
1325                 my $tle_label = $tle->{'label'};
1326                 my $tle_barcode = $tle->{'barcode'};
1327                 if (defined $tle_barcode) {
1328                     $tle_label =~ s/$tle_barcode/SUBSTITUTE_BARCODE/g;
1329                 }
1330                 $existing_labels{$tle_label} = 1 if defined $tle_label;
1331             }
1332         }
1333
1334         my $nlabels = 10 ** $npercents;
1335         my ($i);
1336         for ($i = 1; $i < $nlabels; $i++) {
1337             $label = sprintf($sprintf_pat, $i);
1338             last unless (exists $existing_labels{$label});
1339         }
1340
1341         # susbtitute the barcode and slot
1342         $label =~ s/SUBSTITUTE_BARCODE/$barcode/g;
1343         if ($template =~ /SUBSTITUTE_SLOT/) {
1344             my $slot_label = sprintf("%0*d", $slot_digit, $slot);
1345             $label =~ s/SUBSTITUTE_SLOT/$slot_label/g;
1346         }
1347
1348         # bail out if we didn't find an unused label
1349         return (undef, "Can't label unlabeled volume: All label used")
1350                 if ($i >= $nlabels);
1351     }
1352
1353     # verify $label matches $labelstr
1354     if ($label !~ /$labelstr/) {
1355         return (undef, "Newly-generated label '$label' does not match labelstr '$labelstr'");
1356     }
1357
1358     if (!$label) {
1359         return (undef, "Generated label is empty");
1360     }
1361
1362     return $label;
1363 }
1364
1365 sub make_new_meta_label {
1366     my $self = shift;
1367     my %params = @_;
1368
1369     my $tl = $self->{'tapelist'};
1370     die ("make_new_meta_label: no tapelist") if !$tl;
1371     return undef if !defined $self->{'meta_autolabel'};
1372     my $template = $self->{'meta_autolabel'};
1373     return if !defined $template;
1374
1375     if (!$template) {
1376         return (undef, "template is not set, you must set meta-autolabel");
1377     }
1378     $template =~ s/\$\$/SUBSTITUTE_DOLLAR/g;
1379     $template =~ s/\$o/SUBSTITUTE_ORG/g;
1380     $template =~ s/\$c/SUBSTITUTE_CONFIG/g;
1381
1382     my $org = getconf($CNF_ORG);
1383     my $config = Amanda::Config::get_config_name();
1384
1385     $template =~ s/SUBSTITUTE_DOLLAR/\$/g;
1386     $template =~ s/SUBSTITUTE_ORG/$org/g;
1387     $template =~ s/SUBSTITUTE_CONFIG/$config/g;
1388
1389     (my $npercents =
1390         $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
1391     $npercents = 0 if $npercents eq $template;
1392     my $nlabels = 10 ** $npercents;
1393
1394     # make up a sprintf pattern
1395     (my $sprintf_pat = $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
1396
1397     my %existing_meta_labels =
1398         map { $_->{'meta'} => 1 } @{$tl->{'tles'}};
1399
1400     my ($i, $meta);
1401     for ($i = 1; $i < $nlabels; $i++) {
1402         $meta = sprintf($sprintf_pat, $i);
1403         last unless (exists $existing_meta_labels{$meta});
1404     }
1405
1406     # bail out if we didn't find an unused label
1407     return (undef, "Can't label unlabeled meta volume: All meta label used")
1408                 if ($i >= $nlabels);
1409
1410     if (!$meta) {
1411         return (undef, "Generated meta-label is empty");
1412     }
1413
1414     return $meta;
1415 }
1416
1417 sub volume_is_labelable {
1418     my $self = shift;
1419     my $dev_status  = shift;
1420     my $f_type = shift;
1421     my $label = shift;
1422     my $autolabel = $self->{'autolabel'};
1423
1424     if (!defined $dev_status) {
1425         return 0;
1426     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
1427              defined $f_type and
1428              $f_type == $Amanda::Header::F_EMPTY) {
1429         return 0 if (!$autolabel->{'empty'});
1430     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
1431              defined $f_type and
1432              $f_type == $Amanda::Header::F_WEIRD) {
1433         return 0 if (!$autolabel->{'non_amanda'});
1434     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_ERROR) {
1435         return 0 if (!$autolabel->{'volume_error'});
1436     } elsif ($dev_status != $DEVICE_STATUS_SUCCESS) {
1437         return 0;
1438     } elsif ($dev_status & $DEVICE_STATUS_SUCCESS and
1439              $f_type == $Amanda::Header::F_TAPESTART and
1440              $label !~ /$self->{'labelstr'}/) {
1441         return 0 if (!$autolabel->{'other_config'});
1442     }
1443
1444     return 1;
1445 }
1446
1447 package Amanda::Changer::Error;
1448 use Amanda::Debug qw( :logging );
1449 use Carp qw( cluck );
1450 use Amanda::Debug;
1451 use overload
1452     '""' => sub { $_[0]->{'message'}; },
1453     'cmp' => sub { $_[0]->{'message'} cmp $_[1]; };
1454
1455 my %known_err_types = map { ($_, 1) } qw( fatal failed );
1456 my %known_err_reasons = map { ($_, 1) } qw( notfound invalid notimpl driveinuse volinuse unknown device empty );
1457
1458 sub new {
1459     my $class = shift; # ignore class
1460     my ($type, %info) = @_;
1461
1462     my $reason = "";
1463     $reason = ", reason='$info{reason}'" if $type eq "failed";
1464     debug("new Amanda::Changer::Error: type='$type'$reason, message='$info{message}'");
1465
1466     $info{'type'} = $type;
1467
1468     # do some sanity checks.  Note that these sanity checks issue a warning
1469     # with cluck, but add default values to the error.  This is in the hope
1470     # that an unusual Amanda error is not obscured by a problem in the
1471     # make_error invocation.  The stack trace produced by cluck should help to
1472     # track down the bad make_error invocation.
1473
1474     if (!exists $info{'message'}) {
1475         cluck("no message given to A::C::make_error");
1476         $info{'message'} = "unknown error";
1477     }
1478
1479     if (!exists $known_err_types{$type}) {
1480         cluck("invalid Amanda::Changer::Error type '$type'");
1481         $type = 'fatal';
1482     }
1483
1484     if ($type eq 'failed' and !exists $info{'reason'}) {
1485         cluck("no reason given to A::C::make_error");
1486         $info{'reason'} = "unknown";
1487     }
1488
1489     if ($type eq 'failed' and !exists $known_err_reasons{$info{'reason'}}) {
1490         cluck("invalid Amanda::Changer::Error reason '$info{reason}'");
1491         $info{'reason'} = 'unknown';
1492     }
1493
1494     return bless (\%info, $class);
1495 }
1496
1497 # do nothing in quit
1498 sub quit {}
1499
1500 # types
1501 sub fatal { $_[0]->{'type'} eq 'fatal'; }
1502 sub failed { $_[0]->{'type'} eq 'failed'; }
1503
1504 # reasons
1505 sub notfound { $_[0]->failed && $_[0]->{'reason'} eq 'notfound'; }
1506 sub invalid { $_[0]->failed && $_[0]->{'reason'} eq 'invalid'; }
1507 sub notimpl { $_[0]->failed && $_[0]->{'reason'} eq 'notimpl'; }
1508 sub driveinuse { $_[0]->failed && $_[0]->{'reason'} eq 'driveinuse'; }
1509 sub volinuse { $_[0]->failed && $_[0]->{'reason'} eq 'volinuse'; }
1510 sub unknown { $_[0]->failed && $_[0]->{'reason'} eq 'unknown'; }
1511 sub empty { $_[0]->failed && $_[0]->{'reason'} eq 'empty'; }
1512
1513 # slot accessor
1514 sub slot { $_[0]->{'slot'}; }
1515
1516 package Amanda::Changer::Reservation;
1517 # this is a simple base class with stub method or two.
1518 use Amanda::Config qw( :getconf );
1519
1520 sub new {
1521     my $class = shift;
1522     my $self = {
1523         released => 0,
1524     };
1525     return bless ($self, $class)
1526 }
1527
1528 sub DESTROY {
1529     my ($self) = @_;
1530     if (!$self->{'released'}) {
1531         if (defined $self->{this_slot}) {
1532             Amanda::Debug::warning("Changer reservation for slot '$self->{this_slot}' has " .
1533                                    "gone out of scope without release");
1534         } else {
1535             Amanda::Debug::warning("Changer reservation for unknown slot has " .
1536                                    "gone out of scope without release");
1537         }
1538     }
1539 }
1540
1541 sub set_label {
1542     my $self = shift;
1543     my %params = @_;
1544
1545     # nothing to do by default: just call the finished callback
1546     if (exists $params{'finished_cb'}) {
1547         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1548     }
1549 }
1550
1551 sub release {
1552     my $self = shift;
1553     my %params = @_;
1554
1555     if ($self->{'released'}) {
1556         $params{'finished_cb'}->(undef) if exists $params{'finished_cb'};
1557         return;
1558     }
1559
1560     # always finish the device on release; it's illegal for anything
1561     # else to use the device after this point, anyway, so we want to
1562     # release the device's resources immediately
1563     if (defined $self->{'device'}) {
1564         $self->{'device'}->finish();
1565     }
1566
1567     $self->{'released'} = 1;
1568     $self->do_release(%params);
1569 }
1570
1571 sub do_release {
1572     my $self = shift;
1573     my %params = @_;
1574
1575     # this is the one subclasses should override
1576
1577     if (exists $params{'finished_cb'}) {
1578         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1579     }
1580 }
1581
1582 sub get_meta_label {
1583     my $self = shift;
1584     my %params = @_;
1585
1586     # this is the one subclasses should override
1587
1588     if (exists $params{'finished_cb'}) {
1589         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1590     }
1591 }
1592
1593 sub set_meta_label {
1594     my $self = shift;
1595     my %params = @_;
1596
1597     # this is the one subclasses should override
1598
1599     if (exists $params{'finished_cb'}) {
1600         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1601     }
1602 }
1603
1604 sub make_new_tape_label {
1605     my $self = shift;
1606     my %params = @_;
1607
1608     $params{'barcode'} = $self->{'barcode'} if !defined $params{'barcode'};
1609     $params{'meta'} = $self->{'meta'} if !defined $params{'meta'};
1610     $params{'slot'} = $self->{'this_slot'} if !defined $params{'slot'};
1611     return $self->{'chg'}->make_new_tape_label(%params);
1612 }
1613
1614
1615 sub make_new_meta_label {
1616     my $self = shift;
1617     my %params = @_;
1618
1619     return $self->{'chg'}->make_new_meta_label(%params);
1620 }
1621
1622 package Amanda::Changer::Config;
1623 use Amanda::Config qw( :getconf string_to_boolean );
1624 use Amanda::Device qw( :constants );
1625
1626 sub new {
1627     my $class = shift;
1628     my ($cc) = @_;
1629
1630     my $self = bless {}, $class;
1631
1632     if (defined $cc) {
1633         $self->{'name'} = changer_config_name($cc);
1634         $self->{'is_global'} = 0;
1635
1636         $self->{'tapedev'} = changer_config_getconf($cc, $CHANGER_CONFIG_TAPEDEV);
1637         $self->{'tpchanger'} = changer_config_getconf($cc, $CHANGER_CONFIG_TPCHANGER);
1638         $self->{'changerdev'} = changer_config_getconf($cc, $CHANGER_CONFIG_CHANGERDEV);
1639         $self->{'changerfile'} = changer_config_getconf($cc, $CHANGER_CONFIG_CHANGERFILE);
1640
1641         $self->{'properties'} = changer_config_getconf($cc, $CHANGER_CONFIG_PROPERTY);
1642         $self->{'device_properties'} = changer_config_getconf($cc, $CHANGER_CONFIG_DEVICE_PROPERTY);
1643     } else {
1644         $self->{'name'} = "default";
1645         $self->{'is_global'} = 1;
1646
1647         $self->{'tapedev'} = getconf($CNF_TAPEDEV);
1648         $self->{'tpchanger'} = getconf($CNF_TPCHANGER);
1649         $self->{'changerdev'} = getconf($CNF_CHANGERDEV);
1650         $self->{'changerfile'} = getconf($CNF_CHANGERFILE);
1651
1652         # no changer or device properties, since there's no changer definition to use
1653         $self->{'properties'} = {};
1654         $self->{'device_properties'} = {};
1655     }
1656     return $self;
1657 }
1658
1659 sub configure_device {
1660     my $self = shift;
1661     my ($device) = @_;
1662
1663     # we'll accumulate properties in this hash *overwriting* previous properties
1664     # instead of appending to them
1665     my %properties;
1666
1667     # always use implicit properties
1668     %properties = ( %properties, %{ $self->_get_implicit_properties() } );
1669
1670     # always use global properties
1671     %properties = ( %properties, %{ getconf($CNF_DEVICE_PROPERTY) } );
1672
1673     # if this is a device alias, add properties from its device definition
1674     if (my $dc = lookup_device_config($device->device_name)) {
1675         %properties = ( %properties,
1676                 %{ device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY); } );
1677     }
1678
1679     # finally, add any props from the changer config
1680     %properties = ( %properties, %{ $self->{'device_properties'} } );
1681
1682     while (my ($propname, $propinfo) = each(%properties)) {
1683         for my $value (@{$propinfo->{'values'}}) {
1684             if (!$device->property_set($propname, $value)) {
1685                 my $msg;
1686                     if ($device->status == $DEVICE_STATUS_SUCCESS) {
1687                         $msg = "Error setting '$propname' on device '".$device->device_name."'";
1688                     } else {
1689                         $msg = $device->error() . " on device '".$device->device_name."'";
1690                     }
1691                 if (exists $propinfo->{'optional'}) {
1692                     if ($propinfo->{'optional'} eq 'warn') {
1693                         warn("$msg (ignored)");
1694                     }
1695                 } else {
1696                     return $msg;
1697                 }
1698             }
1699         }
1700     }
1701
1702     return undef;
1703 }
1704
1705 sub get_property {
1706     my $self = shift;
1707     my ($property) = @_;
1708
1709     my $prophash = $self->{'properties'}->{$property};
1710     return undef unless defined($prophash);
1711
1712     return wantarray? @{$prophash->{'values'}} : $prophash->{'values'}->[0];
1713 }
1714
1715 sub get_boolean_property {
1716     my ($self) = shift;
1717     my ($propname, $default) = @_;
1718
1719     return $default
1720         unless (exists $self->{'properties'}->{$propname});
1721
1722     my $propinfo = $self->{'properties'}->{$propname};
1723     return undef unless @{$propinfo->{'values'}} == 1;
1724     return string_to_boolean($propinfo->{'values'}->[0]);
1725 }
1726
1727 sub _get_implicit_properties {
1728     my $self = shift;
1729     my $props = {};
1730
1731     my $tapetype_name = getconf($CNF_TAPETYPE);
1732     return unless defined($tapetype_name);
1733
1734     my $tapetype = lookup_tapetype($tapetype_name);
1735     return unless defined($tapetype);
1736
1737     # The property hashes used here add the 'optional' key, which indicates
1738     # that the property is implicit and that a failure to set it is not fatal.
1739     # The flag is used by configure_device.
1740     if (tapetype_seen($tapetype, $TAPETYPE_LENGTH)) {
1741         $props->{'max_volume_usage'} = {
1742             optional => 1,
1743             priority => 0,
1744             append => 0,
1745             values => [
1746                 tapetype_getconf($tapetype, $TAPETYPE_LENGTH) * 1024,
1747             ]};
1748     }
1749
1750     if (tapetype_seen($tapetype, $TAPETYPE_READBLOCKSIZE)) {
1751         $props->{'read_block_size'} = {
1752             optional => "warn", # optional, but give a warning
1753             priority => 0,
1754             append => 0,
1755             values => [
1756                 tapetype_getconf($tapetype, $TAPETYPE_READBLOCKSIZE) * 1024,
1757             ]};
1758     }
1759
1760     if (tapetype_seen($tapetype, $TAPETYPE_BLOCKSIZE)) {
1761         $props->{'block_size'} = {
1762             optional => 0,
1763             priority => 0,
1764             append => 0,
1765             values => [
1766                 # convert the length from kb to bytes here
1767                 tapetype_getconf($tapetype, $TAPETYPE_BLOCKSIZE) * 1024,
1768             ]};
1769     }
1770
1771     return $props;
1772 }
1773
1774 1;