Imported Upstream version 3.3.0
[debian/amanda] / perl / Amanda / Changer.pm
1 # Copyright (c) 2007,2008,2009,2010 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                             meta    => $meta);
389
390 To devise a new name for a volume using the C<barcode> and C<meta> arguments.
391 This will return C<undef> if no label could be created.
392
393 =head3 make_new_meta_label
394
395   $chg->make_new_meta_label();
396
397 To devise a new meta name for a meta volume.
398 This will return C<undef> if no label could be created.
399
400 =head3 have_inventory
401
402   $chg->have_inventory() 
403
404 Return True if the changer have the inventory method.
405
406 =head3 volume_is_labelable
407
408   $chg->volume_is_labelable($device_status, $f_type, $label);
409
410 Return 1 if the volume is labelable acording to the autolabel setting.
411
412 =over 4
413
414 =item slot
415
416 The slot name
417
418 =item current
419
420 Set to C<1> if it is the current slot.
421
422 =item state
423
424 Set to C<SLOT_FULL> if the slot is full, C<SLOT_EMPTY> if the slot is empty (no
425 volume in slot), C<SLOT_UNKNOWN> if the changer doesn't know if the slot is full
426 or not (but it can know), or undef if the changer can't know if the slot is full or not.
427 A changer that doesn't keep state must set it to undef, like chg-single.
428 These constants are available in the C<:constants> export tag.
429
430 A blank or erased volume is not the same as an empty slot.
431
432 =item device_status
433
434 The device status after the open or read_label, undef if device status is unknown.
435
436 =item f_type
437
438 The file header type as returned by read_label, only if device_status is DEVICE_STATUS_SUCCESS.
439
440 =item label
441
442 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.
443
444 =item barcode (optional)
445
446 The barcode for the volume in this slot, if barcodes are available.
447
448 =item reserved
449
450 Set to C<1> if this slot is reserved, either by this process or another
451 process.  This is only set for I<exclusive> reservations, meaning that loading
452 the slot would result in an C<volinuse> error.  Devices which can support
453 concurrent access will never set this flag.
454
455 =item loaded_in (optional)
456
457 For changers which have distinct user-visible drives, this gives the drive
458 currently accessing the volume in this slot.
459
460 =item import_export (optional)
461
462 Set to C<1> if this is an import-export slot -- a slot in which the user can
463 easily add or remove volumes.  This information may be useful for operations to
464 bulk-import newly-inserted tapes or bulk-export a set of tapes.
465
466 =back
467
468 =head3 move
469
470   $chg->move(finished_cb => $cb,
471              from_slot => $from,
472              to_slot => $to)
473
474 Move a volume between two slots in the changer. These slots are provided by the
475 user, and have meaning for the changer.
476
477 =head2 RESERVATION OBJECTS
478
479 =head3 Methods
480
481 =head3 $res->{'chg'}
482
483 This is the changer object.
484
485 =head3 $res->{'device'}
486
487 This is the fully configured device for the reserved volume.  The device is not
488 started.
489
490 =head3 $res->{'this_slot'}
491
492 This is the name of this slot.  It is an arbitrary string which will
493 have some meaning to the changer's C<load()> method. It is safe to
494 access this field after the reservation has been released.
495
496 =head3 $res->{'barcode'}
497
498 If this changer supports barcodes, then this is the barcode of the reserved
499 volume.  This can be helpful for labeling tapes using their barcode.
500
501 =head3 $label = $res->make_new_tape_label()
502
503 To devise a new name for a volume.
504 This will return C<undef> if no label could be created.
505
506 =head3 $meta = $res->make_new_meta_label()
507
508 To devise a new meta name for a meta volume.
509 This will return C<undef> if no label could be created.
510
511 =head3 $res->release(finished_cb => $cb, eject => $eject)
512
513 This is how an Amanda application indicates that it no longer needs the
514 reserved volume. The callback is called after any related operations are
515 complete -- possibly immediately. Some drives and changers have a notion of
516 "ejecting" a volume, and some don't. In particular, a manual changer can cause
517 the tape drive to eject the tape, while a tape robot can move a tape back to
518 storage, leaving the drive empty. If the eject parameter is given and true, it
519 indicates that Amanda is done with the volume and has reason to believe the
520 user is done with the volume, too -- for example, when a tape has been written
521 completely.
522
523 A reservation will be released automatically when the object is destroyed, but
524 in this case no finished_cb is given, so the release operation may not complete
525 before the process exits. Wherever possible, reservations should be explicitly
526 released.
527
528 =head3 $res->set_label(finished_cb => $cb, label => $label)
529
530 This is how Amanda indicates to the changer that the volume in the device has
531 been (re-)labeled. Changers can keep a database of volume labels by slot or by
532 barcode, or just ignore this function and call $cb immediately. Note that the
533 reservation must still be held when this function is called.
534
535 =head1 SUBCLASS HELPERS
536
537 C<Amanda::Changer> implements some methods and attributes to help subclass
538 implementers.
539
540 =head2 INFO
541
542 Implementing the C<info> method can be tricky, because it can potentially request
543 a number of keys that require asynchronous access.  The C<info> implementation in
544 this class may make the process a bit easier.
545
546 First, if the method C<info_setup> is defined, C<info> calls it, passing it a
547 C<finished_cb> and the list of desired keys, C<info>.  This method is useful to
548 gather information that is useful for several info keys.
549
550 Next, for each requested key, C<info> calls
551
552   $self->info_key($key, %params)
553
554 including a regular C<info_cb> callback.  The C<info> method will wait for
555 all C<info_key> invocations to finish, then collect the results or errors that
556 occur.
557
558 =head2 ERROR HANDLING
559
560 To create a new error object, use C<< $self->make_error($type, $cb, %args) >>.
561 This method will create a new C<Amanda::Changer::Error> object and optionally
562 invoke a callback with it.  If C<$type> is C<fatal>, then
563 C<< $chg->{'fatal_error'} >> is made a reference to the new error object.  The
564 callback C<$cb> (which should be made using C<make_cb()> from
565 C<Amanda::MainLoop>) is called with the new error object.  The C<%args> are
566 added to the new error object.  In use, this looks something like:
567
568   if (!$success) {
569     return $self->make_error("failed", $params{'res_cb'},
570             reason => "notfound",
571             message => "Volume '$label' not found");
572   }
573
574 This method can also be called as a class method, e.g., from a constructor.
575 In this case, it returns the resulting error object, which should be fatal.
576
577   if (!$config_ok) {
578     return Amanda::Changer->make_error("fatal", undef,
579             message => "config error");
580   }
581
582 For cases where a number of errors have occurred, it is helpful to make a
583 "combined" error.  The method C<make_combined_error> takes care of this
584 operation, given a callback and an array of tuples C<[ $description, $err ]>
585 for each error.  This method uses some heuristics to figure out the
586 appropriate type and reason for the combined error.
587
588   if ($left_err and $right_err) {
589     return $self->make_combined_error($params{'finished_cb'},
590         [ [ "from the left", $left_err ],
591           [ "from the right", $right_err ] ]);
592   }
593
594 Any additional keyword arguments to C<make_combined_error> are put into the
595 combined error; this is useful to set the C<slot> attribute.
596
597 The method C<< $self->check_error($cb) >> is a useful method for subclasses to
598 avoid doing anything after a fatal error.  This method checks
599 C<< $self->{'fatal_error'} >>.  If the error is defined, the method calls C<$cb>
600 and returns true.  The usual recipe is
601
602   sub load {
603     my $self = shift;
604     my %params = @_;
605
606     return if $self->check_error($params{'res_cb'});
607     # ...
608   }
609
610 =head2 CONFIG
611
612 C<Amanda::Changer->new> calls subclass constructors with two parameters: a
613 configuration object and a changer specification.  The changer specification is
614 the string that led to creation of this changer device.  The configuration
615 object is of type C<Amanda::Changer::Config>, and can be treated as a hashref
616 with the following keys:
617
618   name                  -- name of the changer section (or "default")
619   is_global             -- true if this changer is the default changer
620   tapedev               -- tapedev parameter
621   tpchanger             -- tpchanger parameter
622   changerdev            -- changerdev parameter
623   changerfile           -- changerfile parameter
624   properties            -- all properties for this changer
625   device_properties     -- device properties from this changer
626
627 The four parameters are just as supplied by the user, either in the global
628 config or in a changer section.  Changer authors are cautioned not to try to
629 override any of these parameters as previous changers have done (e.g.,
630 C<changerfile> specifying both configuration and state files).  Use properties
631 instead.
632
633 The C<properties> and C<device_properties> parameters are in the format
634 provided by C<Amanda::Config>.  If C<is_global> is true, then
635 C<device_properties> will include any device properties specified globally, as
636 well as properties culled from the global tapetype.
637
638 The C<configure_device> method generally takes care of the intricacies of
639 handling device properties.  Pass it a newly opened device and it will apply
640 the relevant properties, returning undef on success or an error message on
641 failure.
642
643 The C<get_property> method is a shortcut method to get the value of a changer
644 property, ignoring its the priority and other attributes.  In a list context,
645 it returns all values for the property; in a scalar context, it returns the
646 first value specified.
647
648 Many properties are boolean, and Amanda has a habit of accepting a number of
649 different ways of writing boolean values.  The method
650 C<< $config->get_boolean_property($prop, $default) >> will parse such a
651 property, returning 0 or 1 if the property is specified, C<$default> if it is
652 not specified, or C<undef> if the property cannot be parsed.
653
654 =head2 PERSISTENT STATE AND LOCKING
655
656 Many changer subclasses need to track state across invocations and between
657 different processes, and to ensure that the state is read and written
658 atomically.  The C<with_locked_state> provides this functionality by
659 locking a statefile, only unlocking it after any changes have been written back
660 to it.  Subclasses can use this method both for mutual exclusion (ensuring that
661 only one changer operation is in progress at any time) and for atomic state
662 storage.
663
664 The C<with_locked_state> method works like C<synchronized> (in
665 L<Amanda::MainLoop>), but with some extra arguments:
666
667   $self->with_locked_state($filename, $some_cb, sub {
668     # note: $some_cb shadows outer $some_cb; see Amanda::MainLoop::synchronized
669     my ($state, $some_cb) = @_;
670     # ... and eventually:
671     $some_cb->(...);
672   });
673
674 The callback C<$some_cb> is assumed to take a changer error as its first
675 argument, and if there are any errors locking the statefile, they will be
676 reported directly to this callback.  Otherwise, a wrapped version of
677 C<$some_cb> is passed to the inner C<sub>.  When this wrapper is invoked, the
678 state will be written to disk and unlocked before the original callback is
679 invoked.
680
681 The state itself begins as an empty hashref, but subclasses can add arbitrary
682 keys to the hash.  Serialization is currently handled with L<Data::Dumper>.
683
684 =head2 PARAMETER VALIDATION
685
686 The C<validate_params> method is useful to make sure that the proper parameters
687 are present for a particular method, dying if not.  Call it like this:
688
689   $self->validate_params("load", \%params);
690
691 The method currently only supports the "load" method, but can be expanded to
692 cover other methods.
693
694 =head1 SEE ALSO
695
696 The Amanda Wiki (http://wiki.zmanda.com) has a higher-level description of the
697 changer model implemented by this package.
698
699 See amanda-changers(7) for user-level documentation of the changer implementations.
700
701 =cut
702
703 # constants for the states that slots may be in; note that these states still
704 # apply even if the tape is actually loaded in a drive
705
706 # slot is known to contain a volume
707 use constant SLOT_FULL => 1;
708
709 # slot is known to contain no volume
710 use constant SLOT_EMPTY => 2;
711
712 # don't known if slot contains a volume
713 use constant SLOT_UNKNOWN => 3;
714
715 our @EXPORT_OK = qw( SLOT_FULL SLOT_EMPTY SLOT_UNKNOWN );
716 our %EXPORT_TAGS = (
717     constants => [ qw( SLOT_FULL SLOT_EMPTY SLOT_UNKNOWN ) ],
718 );
719
720 # this is a "virtual" constructor which instantiates objects of different
721 # classes based on its argument.  Subclasses should not try to chain up!
722 sub new {
723     shift eq 'Amanda::Changer'
724         or die("Do not call the Amanda::Changer constructor from subclasses");
725     my ($name) = shift;
726     my %params = @_;
727     my ($uri, $cc);
728
729     # creating a named changer is a bit easier
730     if (defined($name)) {
731         # first, is it a changer alias?
732         if (($uri,$cc) = _changer_alias_to_uri($name)) {
733             return _new_from_uri($uri, $cc, $name, %params);
734         }
735
736         # maybe a straight-up changer URI?
737         if (_uri_to_pkgname($name)) {
738             return _new_from_uri($name, undef, $name, %params);
739         }
740
741         # assume it's a device name or alias, and invoke the single-changer
742         return _new_from_uri("chg-single:$name", undef, $name, %params);
743     } else { # !defined($name)
744         if ((getconf_linenum($CNF_TPCHANGER) == -2 ||
745              (getconf_seen($CNF_TPCHANGER) &&
746               getconf_linenum($CNF_TAPEDEV) != -2)) &&
747             getconf($CNF_TPCHANGER) ne '') {
748             my $tpchanger = getconf($CNF_TPCHANGER);
749
750             # first, is it an old changer script?
751             if ($uri = _old_script_to_uri($tpchanger)) {
752                 return _new_from_uri($uri, undef, $tpchanger, %params);
753             }
754
755             # if not, then there had better be no tapdev
756             if (getconf_seen($CNF_TAPEDEV) and getconf($CNF_TAPEDEV) ne '' and
757                 ((getconf_linenum($CNF_TAPEDEV) > 0 and
758                   getconf_linenum($CNF_TPCHANGER) > 0) ||
759                  (getconf_linenum($CNF_TAPEDEV) == -2))) {
760                 return Amanda::Changer::Error->new('fatal',
761                     message => "Cannot specify both 'tapedev' and 'tpchanger' " .
762                         "unless using an old-style changer script");
763             }
764
765             # maybe a changer alias?
766             if (($uri,$cc) = _changer_alias_to_uri($tpchanger)) {
767                 return _new_from_uri($uri, $cc, $tpchanger, %params);
768             }
769
770             # maybe a straight-up changer URI?
771             if (_uri_to_pkgname($tpchanger)) {
772                 return _new_from_uri($tpchanger, undef, $tpchanger, %params);
773             }
774
775             # assume it's a device name or alias, and invoke the single-changer
776             return _new_from_uri("chg-single:$tpchanger", undef, $tpchanger, %params);
777         } elsif (getconf_seen($CNF_TAPEDEV) and getconf($CNF_TAPEDEV) ne '') {
778             my $tapedev = getconf($CNF_TAPEDEV);
779
780             # first, is it a changer alias?
781             if (($uri,$cc) = _changer_alias_to_uri($tapedev)) {
782                 return _new_from_uri($uri, $cc, $tapedev, %params);
783             }
784
785             # maybe a straight-up changer URI?
786             if (_uri_to_pkgname($tapedev)) {
787                 return _new_from_uri($tapedev, undef, $tapedev, %params);
788             }
789
790             # assume it's a device name or alias, and invoke chg-single.
791             # chg-single will check the device immediately and error out
792             # if the device name is invalid.
793             return _new_from_uri("chg-single:$tapedev", undef, $tapedev, %params);
794         } else {
795             return Amanda::Changer::Error->new('fatal',
796                 message => "You must specify one of 'tapedev' or 'tpchanger'");
797         }
798     }
799 }
800
801 sub DESTROY {
802     my $self = shift;
803
804     debug("Changer '$self->{'chg_name'}' not quit") if defined $self->{'chg_name'};
805 }
806
807 # do nothing in quit
808 sub quit {
809     my $self = shift;
810
811     foreach (keys %$self) {
812         delete $self->{$_};
813     }
814 }
815
816 # helper functions for new
817
818 sub _changer_alias_to_uri {
819     my ($name) = @_;
820
821     my $cc = Amanda::Config::lookup_changer_config($name);
822     if ($cc) {
823         my $tpchanger = changer_config_getconf($cc, $CHANGER_CONFIG_TPCHANGER);
824         if ($tpchanger) {
825             if (my $uri = _old_script_to_uri($tpchanger)) {
826                 return ($uri, $cc);
827             }
828         }
829
830         my $seen_tpchanger = changer_config_seen($cc, $CHANGER_CONFIG_TPCHANGER);
831         my $seen_tapedev = changer_config_seen($cc, $CHANGER_CONFIG_TAPEDEV);
832         if ($seen_tpchanger and $seen_tapedev) {
833             return Amanda::Changer::Error->new('fatal',
834                 message => "Cannot specify both 'tapedev' and 'tpchanger' " .
835                     "**unless using an old-style changer script");
836         }
837         if (!$seen_tpchanger and !$seen_tapedev) {
838             return Amanda::Changer::Error->new('fatal',
839                 message => "You must specify one of 'tapedev' or 'tpchanger'");
840         }
841         $tpchanger ||= changer_config_getconf($cc, $CHANGER_CONFIG_TAPEDEV);
842
843         if (_uri_to_pkgname($tpchanger)) {
844             return ($tpchanger, $cc);
845         } else {
846             die "Changer '$name' specifies invalid tpchanger '$tpchanger'";
847         }
848     }
849
850     # not an alias
851     return;
852 }
853
854 sub _old_script_to_uri {
855     my ($name) = @_;
856
857     die("empty changer script name") unless $name;
858
859     if ((-x "$amlibexecdir/$name") or (($name =~ qr{^/}) and (-x $name))) {
860         return "chg-compat:$name"
861     }
862
863     # not an old script
864     return;
865 }
866
867 # try to load the package for the given URI.  $@ is set properly
868 # if this function returns a false value.
869 sub _uri_to_pkgname {
870     my ($name) = @_;
871
872     my ($type) = ($name =~ /^chg-([A-Za-z_]+):/);
873     if (!defined $type) {
874         $@ = "'$name' is not a changer URI";
875         return 0;
876     }
877
878     $type =~ tr/A-Z-/a-z_/;
879
880     # create a package name to see if it's already imported
881     my $pkgname = "Amanda::Changer::$type";
882     my $filename = $pkgname;
883     $filename =~ s|::|/|g;
884     $filename .= '.pm';
885     return $pkgname if (exists $INC{$filename});
886
887     # try loading it
888     eval "use $pkgname;";
889     if ($@) {
890         my $err = $@;
891
892         # determine whether the module doesn't exist at all, or if there was an
893         # error loading it; die if we found a syntax error
894         if (exists $INC{$filename} or $err =~ /did not return a true value/) {
895             die($err);
896         }
897
898         return 0;
899     }
900
901     return $pkgname;
902 }
903
904 sub _new_from_uri { # (note: this sub is patched by the installcheck)
905     my $uri = shift;
906     my $cc = shift;
907     my $name = shift;
908     my %params = @_;
909
910     # as a special case, if the URI came back as an error, just pass
911     # that along.  This lets the _xxx_to_uri methods return errors more
912     # easily
913     if (ref $uri and $uri->isa("Amanda::Changer::Error")) {
914         return $uri;
915     }
916
917     # make up a key for our hash of already-instantiated objects,
918     # using a newline as a separator, since perl can't use tuples
919     # as keys
920     my $uri_cc = "$uri\n";
921     if (defined $cc) {
922         $uri_cc = $uri_cc . changer_config_name($cc);
923     }
924
925     # return a pre-existing changer, if possible
926
927     # look up the type and load the class
928     my $pkgname = _uri_to_pkgname($uri);
929     if (!$pkgname) {
930         die $@;
931     }
932
933     my $rv = eval {$pkgname->new(Amanda::Changer::Config->new($cc), $uri);};
934     die "$pkgname->new return undef" if $@;
935     die "$pkgname->new did not return an Amanda::Changer object or an Amanda::Changer::Error"
936         unless ($rv->isa("Amanda::Changer") or $rv->isa("Amanda::Changer::Error"));
937
938     if ($rv->isa("Amanda::Changer::Error")) {
939         return $rv;
940     }
941
942     if ($rv->isa("Amanda::Changer")) {
943         # add an instance variable or two
944         $rv->{'fatal_error'} = undef;
945     }
946
947     $rv->{'tapelist'} = $params{'tapelist'};
948     $rv->{'autolabel'} = $params{'autolabel'};
949     $rv->{'autolabel'} = getconf($CNF_AUTOLABEL)
950         unless defined $rv->{'autolabel'};
951     $rv->{'labelstr'} = $params{'labelstr'};
952     $rv->{'labelstr'} = getconf($CNF_LABELSTR)
953         unless defined $rv->{'labelstr'};
954     $rv->{'meta_autolabel'} = $params{'meta_autolabel'};
955     $rv->{'meta_autolabel'} = getconf($CNF_META_AUTOLABEL)
956         unless defined $rv->{'meta_autolabel'};
957     $rv->{'chg_name'} = $name;
958     return $rv;
959 }
960
961 # method stubs that return a "notimpl" error
962
963 sub _stubop {
964     my ($op, $cbname, $self, %params) = @_;
965     return if $self->check_error($params{$cbname});
966
967     my $class = ref($self);
968     my $chg_foo = "chg-" . ($class =~ /Amanda::Changer::(.*)/)[0];
969     return $self->make_error("failed", $params{$cbname},
970         reason => "notimpl",
971         message => "'$chg_foo:' does not support $op");
972 }
973
974 sub load { _stubop("loading volumes", "res_cb", @_); }
975 sub reset { _stubop("reset", "finished_cb", @_); }
976 sub clean { _stubop("clean", "finished_cb", @_); }
977 sub eject { _stubop("eject", "finished_cb", @_); }
978 sub update { _stubop("update", "finished_cb", @_); }
979 sub inventory { _stubop("inventory", "inventory_cb", @_); }
980 sub move { _stubop("move", "finished_cb", @_); }
981 sub set_meta_label { _stubop("set_meta_label", "finished_cb", @_); }
982 sub get_meta_label { _stubop("get_meta_label", "finished_cb", @_); }
983
984 sub have_inventory {
985     my $self = shift;
986
987     return $self->can("inventory") ne \&Amanda::Changer::inventory;
988 }
989
990 # info calls out to info_setup and info_key; see POD above
991 sub info {
992     my $self = shift;
993     my %params = @_;
994
995     if (!$self->can('info_key')) {
996         my $class = ref($self);
997         $params{'info_cb'}->("$class does not support info()");
998         return;
999     }
1000
1001     my ($do_setup, $start_keys, $all_done);
1002
1003     $do_setup = sub {
1004         if ($self->can('info_setup')) {
1005             $self->info_setup(info => $params{'info'},
1006                               finished_cb => sub {
1007                 my ($err) = @_;
1008                 if ($err) {
1009                     $params{'info_cb'}->($err);
1010                 } else {
1011                     $start_keys->();
1012                 }
1013             });
1014         } else {
1015             $start_keys->();
1016         }
1017     };
1018
1019     $start_keys = sub {
1020         my $remaining_keys = 1;
1021         my %key_results;
1022
1023         my $maybe_done = sub {
1024             return if (--$remaining_keys);
1025             $all_done->(%key_results);
1026         };
1027
1028         for my $key (@{$params{'info'}}) {
1029             $remaining_keys++;
1030             $self->info_key($key, info_cb => sub {
1031                 $key_results{$key} = [ @_ ];
1032                 $maybe_done->();
1033             });
1034         }
1035
1036         # we started with $remaining_keys = 1, so decrement it now
1037         $maybe_done->();
1038     };
1039
1040     $all_done = sub {
1041         my %key_results = @_;
1042
1043         # if there are *any* errors, handle them
1044         my @annotated_errs =
1045             map { [ sprintf("While getting info key '%s'", $_), $key_results{$_}->[0] ] }
1046             grep { defined($key_results{$_}->[0]) }
1047             keys %key_results;
1048
1049         if (@annotated_errs) {
1050             return $self->make_combined_error(
1051                 $params{'info_cb'}, [ @annotated_errs ]);
1052         }
1053
1054         # no errors, so combine the results and return them
1055         my %info;
1056         while (my ($key, $result) = each(%key_results)) {
1057             my ($err, %key_info) = @$result;
1058             if (exists $key_info{$key}) {
1059                 $info{$key} = $key_info{$key};
1060             } else {
1061                 warn("No value available for $key");
1062             }
1063         }
1064
1065         $params{'info_cb'}->(undef, %info);
1066     };
1067
1068     $do_setup->();
1069 }
1070
1071 # subclass helpers
1072
1073 sub make_error {
1074     my $self = shift;
1075     my ($type, $cb, %args) = @_;
1076
1077     my $classmeth = $self eq "Amanda::Changer";
1078
1079     if ($classmeth and $type ne 'fatal') {
1080         cluck("type must be fatal when calling make_error as a class method");
1081         $type = 'fatal';
1082     }
1083
1084     my $err = Amanda::Changer::Error->new($type, %args);
1085
1086     if (!$classmeth) {
1087         $self->{'fatal_error'} = $err
1088             if ($err->fatal);
1089
1090         $cb->($err);
1091     }
1092
1093     return $err;
1094 }
1095
1096 sub make_combined_error {
1097     my $self = shift;
1098     my ($cb, $suberrors, %extra_args) = @_;
1099     my $err;
1100
1101     if (@$suberrors == 0) {
1102         die("make_combined_error called with no errors");
1103     }
1104
1105     my $classmeth = $self eq "Amanda::Changer";
1106
1107     # if there's only one suberror, just use it directly
1108     if (@$suberrors == 1) {
1109         $err = $suberrors->[0][1];
1110         die("$err is not an Error object")
1111             unless defined($err) and $err->isa("Amanda::Changer::Error");
1112
1113         $err = Amanda::Changer::Error->new(
1114             $err->{'type'},
1115             reason => $err->{'reason'},
1116             message => $suberrors->[0][0] . ": " . $err->{'message'});
1117     } else {
1118         my $fatal = $classmeth or grep { $_->[1]{'fatal'} } @$suberrors;
1119
1120         my $reason;
1121         if (!$fatal) {
1122             my %reasons =
1123                 map { ($_->[1]{'reason'}, undef) }
1124                 grep { $_->[1]{'reason'} }
1125                 @$suberrors;
1126             if ((keys %reasons) == 1) {
1127                 $reason = (keys %reasons)[0];
1128             } else {
1129                 $reason = 'unknown'; # multiple or 0 "source" reasons
1130             }
1131         }
1132
1133         my $message = join("; ",
1134             map { sprintf("%s: %s", @$_) }
1135             @$suberrors);
1136
1137         my %errargs = ( message => $message, %extra_args );
1138         $errargs{'reason'} = $reason unless ($fatal);
1139         $err = Amanda::Changer::Error->new(
1140             $fatal? "fatal" : "failed",
1141             %errargs);
1142     }
1143
1144     if (!$classmeth) {
1145         $self->{'fatal_error'} = $err
1146             if ($err->fatal);
1147
1148         $cb->($err) if $cb;
1149     }
1150
1151     return $err;
1152 }
1153
1154 sub check_error {
1155     my $self = shift;
1156     my ($cb) = @_;
1157
1158     if (defined $self->{'fatal_error'}) {
1159         $cb->($self->{'fatal_error'}) if $cb;
1160         return 1;
1161     }
1162 }
1163
1164 sub lock_statefile {
1165     my $self = shift;
1166     my %params = @_;
1167
1168     my $statefile = $params{'statefile_filename'};
1169     my $lock_cb = $params{'lock_cb'};
1170     Amanda::Changer::StateFile->new($statefile, $lock_cb);
1171 }
1172
1173 sub with_locked_state {
1174     my $self = shift;
1175     my ($statefile, $cb, $sub) = @_;
1176     my ($filelock, $STATE);
1177     my $poll = 0; # first delay will be 0.1s; see below
1178
1179     my $steps = define_steps
1180         cb_ref => \$cb;
1181
1182     step open => sub {
1183         $filelock = Amanda::Util::file_lock->new($statefile);
1184
1185         $steps->{'lock'}->();
1186     };
1187
1188     step lock => sub {
1189         my $rv = $filelock->lock();
1190         if ($rv == 1) {
1191             # loop until we get the lock, increasing $poll to 10s
1192             $poll += 100 unless $poll >= 10000;
1193             return Amanda::MainLoop::call_after($poll, $steps->{'lock'});
1194         } elsif ($rv == -1) {
1195             return $self->make_error("fatal", $cb,
1196                     message => "Error locking '$statefile'");
1197         }
1198
1199         $steps->{'read'}->();
1200     };
1201
1202     step read => sub {
1203         my $contents = $filelock->data();
1204         if ($contents) {
1205             eval $contents;
1206             if ($@) {
1207                 # $fh goes out of scope here, and is thus automatically
1208                 # unlocked
1209                 return $cb->("error reading '$statefile': $@", undef);
1210             }
1211             if (!defined $STATE or ref($STATE) ne 'HASH') {
1212                 return $cb->("'$statefile' did not define \$STATE properly", undef);
1213             }
1214         } else {
1215             # initial state (blank file)
1216             $STATE = {};
1217         }
1218
1219         $sub->($STATE, $steps->{'cb_wrap'});
1220     };
1221
1222     step cb_wrap =>  sub {
1223         my @args = @_;
1224
1225         my $dumper = Data::Dumper->new([ $STATE ], ["STATE"]);
1226         $dumper->Purity(1);
1227         $filelock->write($dumper->Dump);
1228         $filelock->unlock();
1229
1230         # call through to the original callback with the original
1231         # arguments
1232         $cb->(@args);
1233     };
1234 }
1235
1236 sub validate_params {
1237     my ($self, $op, $params) = @_;
1238
1239     if ($op eq 'load') {
1240         unless(exists $params->{'label'} || exists $params->{'slot'} ||
1241                exists $params->{'relative_slot'}) {
1242                 confess "Invalid parameters to 'load'";
1243         }
1244     } else {
1245         confess "don't know how to validate '$op'";
1246     }
1247 }
1248
1249 sub make_new_tape_label {
1250     my $self = shift;
1251     my %params = @_;
1252
1253     my $tl = $self->{'tapelist'};
1254     die ("make_new_tape_label: no tapelist") if !$tl;
1255     return undef if !defined $self->{'autolabel'}->{'template'};
1256     return undef if !defined $self->{'labelstr'};
1257     my $template = $self->{'autolabel'}->{'template'};
1258     my $labelstr = $self->{'labelstr'};
1259
1260     if (!$template) {
1261         return (undef, "template is not set, you must set autolabel");
1262     }
1263     $template =~ s/\$\$/SUBSTITUTE_DOLLAR/g;
1264     $template =~ s/\$b/SUBSTITUTE_BARCODE/g;
1265     $template =~ s/\$m/SUBSTITUTE_META/g;
1266     $template =~ s/\$o/SUBSTITUTE_ORG/g;
1267     $template =~ s/\$c/SUBSTITUTE_CONFIG/g;
1268
1269     my $org = getconf($CNF_ORG);
1270     my $config = Amanda::Config::get_config_name();
1271     my $barcode = $params{'barcode'};
1272     $barcode = '' if !defined $barcode;
1273     my $meta = $params{'meta'};
1274     $meta = $self->make_new_meta_label(%params) if !defined $meta;
1275     $meta = '' if !defined $meta;
1276
1277     $template =~ s/SUBSTITUTE_DOLLAR/\$/g;
1278     $template =~ s/SUBSTITUTE_ORG/$org/g;
1279     $template =~ s/SUBSTITUTE_CONFIG/$config/g;
1280     $template =~ s/SUBSTITUTE_META/$meta/g;
1281     # Do not susbtitute the barcode now
1282
1283     (my $npercents =
1284         $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
1285     my $nlabels = 10 ** $npercents;
1286
1287     my $label;
1288     if ($npercents == 0) {
1289         if ($template =~ /SUBSTITUTE_BARCODE/ && defined $barcode) {
1290             $label = $template;
1291             $label =~ s/SUBSTITUTE_BARCODE/$barcode/g;
1292             if ($tl->lookup_tapelabel($label)) {
1293                 return (undef, "Label '$label' already exists");
1294             }
1295         } elsif ($template =~ /SUBSTITUTE_BARCODE/ && !defined $barcode) {
1296             return (undef, "Can't generate new label because volume have no barcode");
1297         } else {
1298             return (undef, "autolabel require at least one '%'");
1299         }
1300     } else {
1301         # make up a sprintf pattern
1302         (my $sprintf_pat =
1303             $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
1304
1305         my %existing_labels;
1306         for my $tle (@{$tl->{'tles'}}) {
1307             if (defined $tle && defined $tle->{'label'}) {
1308                 my $tle_label = $tle->{'label'};
1309                 my $tle_barcode = $tle->{'barcode'};
1310                 if (defined $tle_barcode) {
1311                     $tle_label =~ s/$tle_barcode/SUBSTITUTE_BARCODE/g;
1312                 }
1313                 $existing_labels{$tle_label} = 1 if defined $tle_label;
1314             }
1315         }
1316
1317         my ($i);
1318         for ($i = 1; $i < $nlabels; $i++) {
1319             $label = sprintf($sprintf_pat, $i);
1320             last unless (exists $existing_labels{$label});
1321         }
1322         # susbtitute the barcode
1323
1324         $label =~ s/SUBSTITUTE_BARCODE/$barcode/g;
1325
1326         # bail out if we didn't find an unused label
1327         return (undef, "Can't label unlabeled volume: All label used")
1328                 if ($i >= $nlabels);
1329     }
1330
1331     # verify $label matches $labelstr
1332     if ($label !~ /$labelstr/) {
1333         return (undef, "Newly-generated label '$label' does not match labelstr '$labelstr'");
1334     }
1335
1336     if (!$label) {
1337         return (undef, "Generated label is empty");
1338     }
1339
1340     return $label;
1341 }
1342
1343 sub make_new_meta_label {
1344     my $self = shift;
1345     my %params = @_;
1346
1347     my $tl = $self->{'tapelist'};
1348     die ("make_new_meta_label: no tapelist") if !$tl;
1349     return undef if !defined $self->{'meta_autolabel'};
1350     my $template = $self->{'meta_autolabel'};
1351     return if !defined $template;
1352
1353     if (!$template) {
1354         return (undef, "template is not set, you must set meta-autolabel");
1355     }
1356     $template =~ s/\$\$/SUBSTITUTE_DOLLAR/g;
1357     $template =~ s/\$o/SUBSTITUTE_ORG/g;
1358     $template =~ s/\$c/SUBSTITUTE_CONFIG/g;
1359
1360     my $org = getconf($CNF_ORG);
1361     my $config = Amanda::Config::get_config_name();
1362
1363     $template =~ s/SUBSTITUTE_DOLLAR/\$/g;
1364     $template =~ s/SUBSTITUTE_ORG/$org/g;
1365     $template =~ s/SUBSTITUTE_CONFIG/$config/g;
1366
1367     (my $npercents =
1368         $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
1369     my $nlabels = 10 ** $npercents;
1370
1371     # make up a sprintf pattern
1372     (my $sprintf_pat = $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
1373
1374     my %existing_meta_labels =
1375         map { $_->{'meta'} => 1 } @{$tl->{'tles'}};
1376
1377     my ($i, $meta);
1378     for ($i = 1; $i < $nlabels; $i++) {
1379         $meta = sprintf($sprintf_pat, $i);
1380         last unless (exists $existing_meta_labels{$meta});
1381     }
1382
1383     # bail out if we didn't find an unused label
1384     return (undef, "Can't label unlabeled meta volume: All meta label used")
1385                 if ($i >= $nlabels);
1386
1387     if (!$meta) {
1388         return (undef, "Generated meta-label is empty");
1389     }
1390
1391     return $meta;
1392 }
1393
1394 sub volume_is_labelable {
1395     my $self = shift;
1396     my $dev_status  = shift;
1397     my $f_type = shift;
1398     my $label = shift;
1399     my $autolabel = $self->{'autolabel'};
1400
1401     if (!defined $dev_status) {
1402         return 0;
1403     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
1404         $f_type == $Amanda::Header::F_EMPTY) {
1405         return 0 if (!$autolabel->{'empty'});
1406     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
1407              $f_type == $Amanda::Header::F_WEIRD) {
1408         return 0 if (!$autolabel->{'non_amanda'});
1409     } elsif ($dev_status & $DEVICE_STATUS_VOLUME_ERROR) {
1410         return 0 if (!$autolabel->{'volume_error'});
1411     } elsif ($dev_status != $DEVICE_STATUS_SUCCESS) {
1412         return 0;
1413     } elsif ($dev_status & $DEVICE_STATUS_SUCCESS and
1414              $f_type == $Amanda::Header::F_TAPESTART and
1415              $label !~ /$self->{'labelstr'}/) {
1416         return 0 if (!$autolabel->{'other_config'});
1417     }
1418
1419     return 1;
1420 }
1421
1422 package Amanda::Changer::Error;
1423 use Amanda::Debug qw( :logging );
1424 use Carp qw( cluck );
1425 use Amanda::Debug;
1426 use overload
1427     '""' => sub { $_[0]->{'message'}; },
1428     'cmp' => sub { $_[0]->{'message'} cmp $_[1]; };
1429
1430 my %known_err_types = map { ($_, 1) } qw( fatal failed );
1431 my %known_err_reasons = map { ($_, 1) } qw( notfound invalid notimpl driveinuse volinuse unknown device empty );
1432
1433 sub new {
1434     my $class = shift; # ignore class
1435     my ($type, %info) = @_;
1436
1437     my $reason = "";
1438     $reason = ", reason='$info{reason}'" if $type eq "failed";
1439     debug("new Amanda::Changer::Error: type='$type'$reason, message='$info{message}'");
1440
1441     $info{'type'} = $type;
1442
1443     # do some sanity checks.  Note that these sanity checks issue a warning
1444     # with cluck, but add default values to the error.  This is in the hope
1445     # that an unusual Amanda error is not obscured by a problem in the
1446     # make_error invocation.  The stack trace produced by cluck should help to
1447     # track down the bad make_error invocation.
1448
1449     if (!exists $info{'message'}) {
1450         cluck("no message given to A::C::make_error");
1451         $info{'message'} = "unknown error";
1452     }
1453
1454     if (!exists $known_err_types{$type}) {
1455         cluck("invalid Amanda::Changer::Error type '$type'");
1456         $type = 'fatal';
1457     }
1458
1459     if ($type eq 'failed' and !exists $info{'reason'}) {
1460         cluck("no reason given to A::C::make_error");
1461         $info{'reason'} = "unknown";
1462     }
1463
1464     if ($type eq 'failed' and !exists $known_err_reasons{$info{'reason'}}) {
1465         cluck("invalid Amanda::Changer::Error reason '$info{reason}'");
1466         $info{'reason'} = 'unknown';
1467     }
1468
1469     return bless (\%info, $class);
1470 }
1471
1472 # do nothing in quit
1473 sub quit {}
1474
1475 # types
1476 sub fatal { $_[0]->{'type'} eq 'fatal'; }
1477 sub failed { $_[0]->{'type'} eq 'failed'; }
1478
1479 # reasons
1480 sub notfound { $_[0]->failed && $_[0]->{'reason'} eq 'notfound'; }
1481 sub invalid { $_[0]->failed && $_[0]->{'reason'} eq 'invalid'; }
1482 sub notimpl { $_[0]->failed && $_[0]->{'reason'} eq 'notimpl'; }
1483 sub driveinuse { $_[0]->failed && $_[0]->{'reason'} eq 'driveinuse'; }
1484 sub volinuse { $_[0]->failed && $_[0]->{'reason'} eq 'volinuse'; }
1485 sub unknown { $_[0]->failed && $_[0]->{'reason'} eq 'unknown'; }
1486 sub empty { $_[0]->failed && $_[0]->{'reason'} eq 'empty'; }
1487
1488 # slot accessor
1489 sub slot { $_[0]->{'slot'}; }
1490
1491 package Amanda::Changer::Reservation;
1492 # this is a simple base class with stub method or two.
1493 use Amanda::Config qw( :getconf );
1494
1495 sub new {
1496     my $class = shift;
1497     my $self = {
1498         released => 0,
1499     };
1500     return bless ($self, $class)
1501 }
1502
1503 sub DESTROY {
1504     my ($self) = @_;
1505     if (!$self->{'released'}) {
1506         if (defined $self->{this_slot}) {
1507             Amanda::Debug::warning("Changer reservation for slot '$self->{this_slot}' has " .
1508                                    "gone out of scope without release");
1509         } else {
1510             Amanda::Debug::warning("Changer reservation for unknown slot has " .
1511                                    "gone out of scope without release");
1512         }
1513     }
1514 }
1515
1516 sub set_label {
1517     my $self = shift;
1518     my %params = @_;
1519
1520     # nothing to do by default: just call the finished callback
1521     if (exists $params{'finished_cb'}) {
1522         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1523     }
1524 }
1525
1526 sub release {
1527     my $self = shift;
1528     my %params = @_;
1529
1530     return if $self->{'released'};
1531
1532     # always finish the device on release; it's illegal for anything
1533     # else to use the device after this point, anyway, so we want to
1534     # release the device's resources immediately
1535     if (defined $self->{'device'}) {
1536         $self->{'device'}->finish();
1537     }
1538
1539     $self->{'released'} = 1;
1540     $self->do_release(%params);
1541 }
1542
1543 sub do_release {
1544     my $self = shift;
1545     my %params = @_;
1546
1547     # this is the one subclasses should override
1548
1549     if (exists $params{'finished_cb'}) {
1550         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1551     }
1552 }
1553
1554 sub get_meta_label {
1555     my $self = shift;
1556     my %params = @_;
1557
1558     # this is the one subclasses should override
1559
1560     if (exists $params{'finished_cb'}) {
1561         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1562     }
1563 }
1564
1565 sub set_meta_label {
1566     my $self = shift;
1567     my %params = @_;
1568
1569     # this is the one subclasses should override
1570
1571     if (exists $params{'finished_cb'}) {
1572         $params{'finished_cb'}->(undef) if $params{'finished_cb'};
1573     }
1574 }
1575
1576 sub make_new_tape_label {
1577     my $self = shift;
1578     my %params = @_;
1579
1580     $params{'barcode'} = $self->{'barcode'} if !defined $params{'barcode'};
1581     $params{'meta'} = $self->{'meta'} if !defined $params{'meta'};
1582     return $self->{'chg'}->make_new_tape_label(%params);
1583 }
1584
1585
1586 sub make_new_meta_label {
1587     my $self = shift;
1588     my %params = @_;
1589
1590     return $self->{'chg'}->make_new_meta_label(%params);
1591 }
1592
1593 package Amanda::Changer::Config;
1594 use Amanda::Config qw( :getconf string_to_boolean );
1595 use Amanda::Device;
1596
1597 sub new {
1598     my $class = shift;
1599     my ($cc) = @_;
1600
1601     my $self = bless {}, $class;
1602
1603     if (defined $cc) {
1604         $self->{'name'} = changer_config_name($cc);
1605         $self->{'is_global'} = 0;
1606
1607         $self->{'tapedev'} = changer_config_getconf($cc, $CHANGER_CONFIG_TAPEDEV);
1608         $self->{'tpchanger'} = changer_config_getconf($cc, $CHANGER_CONFIG_TPCHANGER);
1609         $self->{'changerdev'} = changer_config_getconf($cc, $CHANGER_CONFIG_CHANGERDEV);
1610         $self->{'changerfile'} = changer_config_getconf($cc, $CHANGER_CONFIG_CHANGERFILE);
1611
1612         $self->{'properties'} = changer_config_getconf($cc, $CHANGER_CONFIG_PROPERTY);
1613         $self->{'device_properties'} = changer_config_getconf($cc, $CHANGER_CONFIG_DEVICE_PROPERTY);
1614     } else {
1615         $self->{'name'} = "default";
1616         $self->{'is_global'} = 1;
1617
1618         $self->{'tapedev'} = getconf($CNF_TAPEDEV);
1619         $self->{'tpchanger'} = getconf($CNF_TPCHANGER);
1620         $self->{'changerdev'} = getconf($CNF_CHANGERDEV);
1621         $self->{'changerfile'} = getconf($CNF_CHANGERFILE);
1622
1623         # no changer or device properties, since there's no changer definition to use
1624         $self->{'properties'} = {};
1625         $self->{'device_properties'} = {};
1626     }
1627     return $self;
1628 }
1629
1630 sub configure_device {
1631     my $self = shift;
1632     my ($device) = @_;
1633
1634     # we'll accumulate properties in this hash *overwriting* previous properties
1635     # instead of appending to them
1636     my %properties;
1637
1638     # always use implicit properties
1639     %properties = ( %properties, %{ $self->_get_implicit_properties() } );
1640
1641     # always use global properties
1642     %properties = ( %properties, %{ getconf($CNF_DEVICE_PROPERTY) } );
1643
1644     # if this is a device alias, add properties from its device definition
1645     if (my $dc = lookup_device_config($device->device_name)) {
1646         %properties = ( %properties,
1647                 %{ device_config_getconf($dc, $DEVICE_CONFIG_DEVICE_PROPERTY); } );
1648     }
1649
1650     # finally, add any props from the changer config
1651     %properties = ( %properties, %{ $self->{'device_properties'} } );
1652
1653     while (my ($propname, $propinfo) = each(%properties)) {
1654         for my $value (@{$propinfo->{'values'}}) {
1655             if (!$device->property_set($propname, $value)) {
1656                 my $msg = "Error setting '$propname' on device '".$device->device_name."'";
1657                 if (exists $propinfo->{'optional'}) {
1658                     if ($propinfo->{'optional'} eq 'warn') {
1659                         warn("$msg (ignored)");
1660                     }
1661                 } else {
1662                     return $msg;
1663                 }
1664             }
1665         }
1666     }
1667
1668     return undef;
1669 }
1670
1671 sub get_property {
1672     my $self = shift;
1673     my ($property) = @_;
1674
1675     my $prophash = $self->{'properties'}->{$property};
1676     return undef unless defined($prophash);
1677
1678     return wantarray? @{$prophash->{'values'}} : $prophash->{'values'}->[0];
1679 }
1680
1681 sub get_boolean_property {
1682     my ($self) = shift;
1683     my ($propname, $default) = @_;
1684
1685     return $default
1686         unless (exists $self->{'properties'}->{$propname});
1687
1688     my $propinfo = $self->{'properties'}->{$propname};
1689     return undef unless @{$propinfo->{'values'}} == 1;
1690     return string_to_boolean($propinfo->{'values'}->[0]);
1691 }
1692
1693 sub _get_implicit_properties {
1694     my $self = shift;
1695     my $props = {};
1696
1697     my $tapetype_name = getconf($CNF_TAPETYPE);
1698     return unless defined($tapetype_name);
1699
1700     my $tapetype = lookup_tapetype($tapetype_name);
1701     return unless defined($tapetype);
1702
1703     # The property hashes used here add the 'optional' key, which indicates
1704     # that the property is implicit and that a failure to set it is not fatal.
1705     # The flag is used by configure_device.
1706     if (tapetype_seen($tapetype, $TAPETYPE_LENGTH)) {
1707         $props->{'max_volume_usage'} = {
1708             optional => 1,
1709             priority => 0,
1710             append => 0,
1711             values => [
1712                 tapetype_getconf($tapetype, $TAPETYPE_LENGTH) * 1024,
1713             ]};
1714     }
1715
1716     if (tapetype_seen($tapetype, $TAPETYPE_READBLOCKSIZE)) {
1717         $props->{'read_block_size'} = {
1718             optional => "warn", # optional, but give a warning
1719             priority => 0,
1720             append => 0,
1721             values => [
1722                 tapetype_getconf($tapetype, $TAPETYPE_READBLOCKSIZE) * 1024,
1723             ]};
1724     }
1725
1726     if (tapetype_seen($tapetype, $TAPETYPE_BLOCKSIZE)) {
1727         $props->{'block_size'} = {
1728             optional => 0,
1729             priority => 0,
1730             append => 0,
1731             values => [
1732                 # convert the length from kb to bytes here
1733                 tapetype_getconf($tapetype, $TAPETYPE_BLOCKSIZE) * 1024,
1734             ]};
1735     }
1736
1737     return $props;
1738 }
1739
1740 1;