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