Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Changer / aggregate.pm
1 # Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
18
19 package Amanda::Changer::aggregate;
20
21 use strict;
22 use warnings;
23 use Carp;
24 use vars qw( @ISA );
25 @ISA = qw( Amanda::Changer );
26
27 use File::Glob qw( :glob );
28 use File::Path;
29 use Amanda::Config qw( :getconf );
30 use Amanda::Paths;
31 use Amanda::Debug qw( debug warning );
32 use Amanda::Util qw( :alternates );
33 use Amanda::Changer;
34 use Amanda::MainLoop;
35 use Amanda::Device qw( :constants );
36
37 =head1 NAME
38
39 Amanda::Changer::aggregate
40
41 =head1 DESCRIPTION
42
43 This changer operates several child changers.
44 Slot are numbered:
45   0:0  changer 0 slot 0
46   0:1  changer 0 slot 1
47   1:0  changer 1 slot 0
48   3:4  changer 3 slot 4
49
50 See the amanda-changers(7) manpage for usage information.
51
52 =cut
53
54 sub new {
55     my $class = shift;
56     my ($config, $tpchanger) = @_;
57     my ($kidspecs) = ( $tpchanger =~ /chg-aggregate:(.*)/ );
58
59     my @kidspecs = Amanda::Util::expand_braced_alternates($kidspecs);
60     if (@kidspecs < 2) {
61         return Amanda::Changer->make_error("fatal", undef,
62             message => "chg-aggregate needs at least two child changers");
63     }
64
65     my @children = map {
66         ($_ eq "ERROR")? "ERROR" : Amanda::Changer->new($_)
67     } @kidspecs;
68
69     my $fail_on_error;
70     if (defined $config->{'properties'}->{'fail-on-error'}) {
71         $fail_on_error = string_to_boolean($config->{'properties'}->{'fail-on-error'}->{'values'}[0]);
72     } else {
73         $fail_on_error = 1;
74     }
75
76     if (grep { $_->isa("Amanda::Changer::Error") } @children) {
77         my @annotated_errs;
78         my $valid = 0;
79         for my $i (0 .. @children-1) {
80             if ($children[$i]->isa("Amanda::Changer::Error")) {
81                 push @annotated_errs,
82                     [ $kidspecs[$i], $children[$i] ];
83             } else {
84                 $valid++;
85             }
86         }
87         if ($valid == 0 || $fail_on_error) {
88             return Amanda::Changer->make_combined_error(
89                 "fatal", [ @annotated_errs ]);
90         }
91     }
92
93     my $state_filename;
94     my $state_filename_prop = $config->{'properties'}->{'state_filename'};
95
96     if (defined $state_filename_prop) {
97         $state_filename = $state_filename_prop->{'values'}[0];
98     }
99     if (!defined $state_filename) {
100         $state_filename = $Amanda::Paths::CONFIG_DIR . '/' . $config->{'name'} . ".state";
101     }
102
103     my $self = {
104         config => $config,
105         child_names => \@kidspecs,
106         children => \@children,
107         num_children => scalar @children,
108         current_slot => undef,
109         state_filename => $state_filename,
110     };
111     bless ($self, $class);
112     return $self;
113 }
114
115 sub quit {
116     my $self = shift;
117
118     # quit each child
119     foreach my $child (@{$self->{'children'}}) {
120         $child->quit();
121     }
122
123     $self->SUPER::quit();
124 }
125
126 sub _get_current_slot
127 {
128     my $self = shift;
129     my $cb = shift;
130
131     $self->with_locked_state($self->{'state_filename'}, $cb, sub {
132         my ($state, $cb) = @_;
133         $self->{'current_slot'} = $state->{'current_slot'};
134         $self->{'current_slot'} = "0:first" if !defined $self->{'current_slot'};
135         $cb->();
136     });
137 }
138
139 sub _set_current_slot
140 {
141     my $self = shift;
142     my $cb = shift;
143
144     $self->with_locked_state($self->{'state_filename'}, $cb, sub {
145         my ($state, $cb) = @_;
146         $state->{'current_slot'} = $self->{'current_slot'};
147         $cb->();
148     });
149 }
150
151 sub load {
152     my $self = shift;
153     my %params = @_;
154     my $aggregate_res;
155
156     return if $self->check_error($params{'res_cb'});
157
158     my $res_cb = $params{'res_cb'};
159     my $orig_slot = $params{'slot'};
160     $self->validate_params('load', \%params);
161
162     my $steps = define_steps
163         cb_ref => \$res_cb;
164
165     step which_slot => sub {
166         if (exists $params{'relative_slot'} &&
167             $params{'relative_slot'} eq "current") {
168             if (defined $self->{'current_slot'}) {
169                 return $steps->{'set_from_current'}->();
170             } else {
171                 return $self->_get_current_slot($steps->{'set_from_current'});
172             }
173         } elsif (exists $params{'relative_slot'} &&
174                  $params{'relative_slot'} eq "next") {
175             if (defined $self->{'current_slot'}) {
176                 return $steps->{'get_inventory_next'}->();
177             } else {
178                 return $self->_get_current_slot($steps->{'get_inventory_next'});
179             }
180         } elsif (exists $params{'label'}) {
181             return $self->inventory(inventory_cb => $steps->{'got_inventory_label'});
182         }
183         return $steps->{'slot_set'}->();
184     };
185
186     step get_inventory_next => sub {
187         return $self->inventory(inventory_cb => $steps->{'got_inventory_next'})
188     };
189
190     step got_inventory_next => sub {
191         my ($err, $inv) = @_;
192         my $slot;
193         if ($err) {
194             $res_cb->($err);
195         }
196         my $found = -1;
197         for my $i (0.. scalar(@$inv)-1) {
198             $slot = @$inv[$i]->{'slot'};
199             if ($slot eq $self->{'current_slot'}) {
200                 $found = $i;
201             } elsif ($found >= 0 && (!exists $params{'except_slots'} ||
202                                 !exists $params{'except_slots'}->{$slot})) {
203                 $orig_slot = $slot;
204                 return $steps->{'slot_set'}->();
205             }
206         }
207         if ($found >= 0) {
208             for my $i (0..($found-1)) {
209                 $slot = @$inv[$i]->{'slot'};
210                 if (!exists($params{'except_slots'}) ||
211                     !exists($params{'except_slots'}->{$slot})) {
212                     $orig_slot = $slot;
213                     return $steps->{'slot_set'}->();
214                 }
215             }
216         }
217         return $self->make_error("failed", $res_cb,
218                 reason => "notfound",
219                 message => "all slots have been loaded");
220     };
221
222     step got_inventory_label => sub {
223         my ($err, $inv) = @_;
224         my $slot;
225         if ($err) {
226             $res_cb->($err);
227         }
228         for my $i (0.. scalar(@$inv)-1) {
229             my $slot = @$inv[$i]->{'slot'};
230             my $label = @$inv[$i]->{'label'};
231             if ($label eq $params{'label'}) {
232                 $orig_slot = $slot;
233                 return $steps->{'slot_set'}->();
234             }
235         }
236         return $self->make_error("failed", $res_cb,
237                 reason => "notfound",
238                 message => "label $params{'label'} not found");
239     };
240
241     step set_from_current => sub {
242         $orig_slot = $self->{'current_slot'};
243         return $steps->{'slot_set'}->();
244     };
245
246     step slot_set => sub {
247         my ($kid, $slot) = split(':', $orig_slot, 2);
248         my $child = $self->{'children'}[$kid];
249         if (!defined $child) {
250             return $self->make_error("failed", $res_cb,
251                 reason => "invalid",
252                 message => "no changer $kid");
253         }
254         delete $params{'relative_slot'};
255         $params{'slot'} = $slot;
256         $params{'res_cb'} = sub {
257             my ($err, $res) = @_;
258             if ($res) {
259                 if ($slot ne "first" && $res->{'this_slot'} != $slot) {
260                     return $self->make_error("failed", $res_cb,
261                         reason => "invalid",
262                         message => "slot doesn't match: $res->{'this_slot'} != $slot");
263                 } else {
264                     $self->{'current_slot'} = "$kid:$res->{'this_slot'}";
265                     $aggregate_res = Amanda::Changer::aggregate::Reservation->new($self, $res, $self->{'current_slot'});
266                     return $self->_set_current_slot($steps->{'done'});
267                 }
268             }
269             return $res_cb->($err, undef);
270         };
271         return $child->load(%params);
272     };
273
274     step done => sub {
275         $res_cb->(undef, $aggregate_res);
276     };
277 }
278
279 sub info_key {
280     my $self = shift;
281     my ($key, %params) = @_;
282
283     return if $self->check_error($params{'info_cb'});
284
285     my $check_and_report_errors = sub {
286         my ($kid_results) = @_;
287
288         if (grep { defined($_->[0]) } @$kid_results) {
289             # we have errors, so collect them and make a "combined" error.
290             my @annotated_errs;
291             my @err_slots;
292             for my $i (0 .. $self->{'num_children'}-1) {
293                 my $kr = $kid_results->[$i];
294                 next unless defined($kr->[0]);
295                 push @annotated_errs,
296                     [ $self->{'child_names'}[$i], $kr->[0] ];
297                 push @err_slots, $kr->[0]->{'slot'}
298                     if (defined $kr->[0] and defined $kr->[0]->{'slot'});
299             }
300
301             my @slotarg;
302             if (@err_slots == $self->{'num_children'}) {
303                 @slotarg = (slot => collapse_braced_alternates([@err_slots]));
304             }
305
306             $self->make_combined_error(
307                 $params{'info_cb'}, [ @annotated_errs ],
308                 @slotarg);
309             return 1;
310         }
311     };
312
313     if ($key eq 'num_slots') {
314         my $all_kids_done_cb = sub {
315             my ($kid_results) = @_;
316             return if ($check_and_report_errors->($kid_results));
317
318             # Sum the result
319             my $num_slots;
320             for (@$kid_results) {
321                 my ($err, %kid_info) = @$_;
322                 next unless exists($kid_info{'num_slots'});
323                 my $kid_num_slots = $kid_info{'num_slots'};
324                 $num_slots += $kid_num_slots;
325             }
326             $params{'info_cb'}->(undef, num_slots => $num_slots) if $params{'info_cb'};
327         };
328
329         $self->_for_each_child(
330             oksub => sub {
331                 my ($kid_chg, $kid_cb) = @_;
332                 $kid_chg->info(info => [ 'num_slots' ], info_cb => $kid_cb);
333             },
334             errsub => undef,
335             parent_cb => $all_kids_done_cb,
336         );
337     } elsif ($key eq "vendor_string") {
338         my $all_kids_done_cb = sub {
339             my ($kid_results) = @_;
340             return if ($check_and_report_errors->($kid_results));
341
342             my @kid_vendors =
343                 grep { defined($_) }
344                 map { my ($e, %r) = @$_; $r{'vendor_string'} }
345                 @$kid_results;
346             my $vendor_string;
347             if (@kid_vendors) {
348                 $vendor_string = collapse_braced_alternates([@kid_vendors]);
349                 $params{'info_cb'}->(undef, vendor_string => $vendor_string) if $params{'info_cb'};
350             } else {
351                 $params{'info_cb'}->(undef) if $params{'info_cb'};
352             }
353         };
354
355         $self->_for_each_child(
356             oksub => sub {
357                 my ($kid_chg, $kid_cb) = @_;
358                 $kid_chg->info(info => [ 'vendor_string' ], info_cb => $kid_cb);
359             },
360             errsub => undef,
361             parent_cb => $all_kids_done_cb,
362         );
363     } elsif ($key eq 'fast_search') {
364         my $all_kids_done_cb = sub {
365             my ($kid_results) = @_;
366             return if ($check_and_report_errors->($kid_results));
367
368             my @kid_fastness =
369                 grep { defined($_) }
370                 map { my ($e, %r) = @$_; $r{'fast_search'} }
371                 @$kid_results;
372             if (@kid_fastness) {
373                 my $fast_search = 1;
374                 # conduct a logical AND of all child fastnesses
375                 for my $f (@kid_fastness) {
376                     $fast_search = $fast_search && $f;
377                 }
378                 $params{'info_cb'}->(undef, fast_search => $fast_search) if $params{'info_cb'};
379             } else {
380                 $params{'info_cb'}->(undef, fast_search => 0) if $params{'info_cb'};
381             }
382         };
383
384         $self->_for_each_child(
385             oksub => sub {
386                 my ($kid_chg, $kid_cb) = @_;
387                 $kid_chg->info(info => [ 'fast_search' ], info_cb => $kid_cb);
388             },
389             errsub => undef,
390             parent_cb => $all_kids_done_cb,
391         );
392     }
393 }
394
395 # reset, clean, etc. are all *very* similar to one another, so we create them
396 # generically
397 sub _mk_simple_op {
398     my ($op, $has_drive) = @_;
399     sub {
400         my $self = shift;
401         my %params = @_;
402
403         return if $self->check_error($params{'finished_cb'});
404
405         if (exists $params{'drive'}) {
406             return $self->make_error("failed", $params{'finished_cb'},
407                     reason => "notimpl",
408                     message => "Can't specify drive fo $op command");
409         }
410
411         my $all_kids_done_cb = sub {
412             my ($kid_results) = @_;
413             if (grep { defined($_->[0]) } @$kid_results) {
414                 # we have errors, so collect them and make a "combined" error.
415                 my @annotated_errs;
416                 for my $i (0 .. $self->{'num_children'}-1) {
417                     my $kr = $kid_results->[$i];
418                     next unless defined($kr->[0]);
419                     push @annotated_errs,
420                         [ $self->{'child_names'}[$i], $kr->[0] ];
421                 }
422                 $self->make_combined_error(
423                     $params{'finished_cb'}, [ @annotated_errs ]);
424                 return 1;
425             }
426             $params{'finished_cb'}->() if $params{'finished_cb'};
427         };
428
429         $self->_for_each_child(
430             oksub => sub {
431                 my ($kid_chg, $kid_cb) = @_;
432                 $kid_chg->$op(%params, finished_cb => $kid_cb);
433             },
434             errsub => undef,
435             parent_cb => $all_kids_done_cb,
436         );
437     };
438 }
439
440 {
441     # perl doesn't like that these symbols are only mentioned once
442     no warnings;
443
444     *reset = _mk_simple_op("reset");
445     *clean = _mk_simple_op("clean");
446     *eject = _mk_simple_op("eject");
447 }
448
449 sub update {
450     my $self = shift;
451     my %params = @_;
452     my %changed;
453     my @kid_args;
454
455     my $user_msg_fn = $params{'user_msg_fn'};
456     $user_msg_fn ||= sub { Amanda::Debug::info("chg-aggregate: " . $_[0]); };
457
458     if (exists $params{'changed'}) {
459         for my $range (split ',', $params{'changed'}) {
460             my ($first, $last) = ($range =~ /([:\d]+)(?:-([:\d]+))?/);
461             $last = $first unless defined($last);
462             if ($first =~ /:/) {
463                 my ($f_kid, $f_slot) = split(':', $first, 2);
464                 my ($l_kid, $l_slot) = split(':', $last, 2);
465                 if ($f_kid != $l_kid) {
466                     return;
467                 }
468                 if ($changed{$f_kid} != 1) {
469                     for my $slot ($f_slot..$l_slot) {
470                         $changed{$f_kid}{$slot} = 1;
471                     }
472                 }
473             } else {
474                 for my $kid ($first..$last) {
475                     $changed{$kid} = 1;
476                 }
477             }
478         }
479         for my $kid (0..$self->{'num_children'}-1) {
480             if ($changed{$kid} == 1) {
481                 $kid_args[$kid] = "ALL";
482             } elsif (keys %{$changed{$kid}} > 0) {
483                 $kid_args[$kid] = { changed => join(',',sort(keys %{$changed{$kid}})) };
484             } else {
485                 $kid_args[$kid] = "NONE";
486             }
487         }
488     } else {
489         for my $kid (0..$self->{'num_children'}-1) {
490             $kid_args[$kid] = "ALL";
491         }
492     }
493
494     my $all_kids_done_cb = sub {
495         my ($kid_results) = @_;
496         if (grep { defined($_->[0]) } @$kid_results) {
497             # we have errors, so collect them and make a "combined" error.
498             my @annotated_errs;
499             for my $i (0 .. $self->{'num_children'}-1) {
500                 my $kr = $kid_results->[$i];
501                 next unless defined($kr->[0]);
502                 push @annotated_errs,
503                     [ $self->{'child_names'}[$i], $kr->[0] ];
504             }
505             $self->make_combined_error(
506                 $params{'finished_cb'}, [ @annotated_errs ]);
507             return 1;
508         }
509         $params{'finished_cb'}->() if $params{'finished_cb'};
510     };
511
512     $self->_for_each_child(
513         oksub => sub {
514             my ($kid_chg, $kid_cb, $args) = @_;
515             if (ref($args) eq "HASH") {
516                 $kid_chg->update(%params, finished_cb => $kid_cb, %$args);
517             } elsif ($args eq "ALL") {
518                 $kid_chg->update(%params, finished_cb => $kid_cb);
519             } else {
520                 $kid_cb->();
521             }
522         },
523         errsub => undef,
524         parent_cb => $all_kids_done_cb,
525         args => \@kid_args,
526     );
527 }
528
529 sub inventory {
530     my $self = shift;
531     my %params = @_;
532
533     return if $self->check_error($params{'inventory_cb'});
534
535     my $steps = define_steps
536         cb_ref => \$params{'inventory_cb'};
537
538     step get_current => sub {
539         return $self->_get_current_slot($steps->{'got_current_slot'});
540     };
541
542     step got_current_slot => sub {
543         $self->_for_each_child(
544             oksub => sub {
545                 my ($kid_chg, $kid_cb) = @_;
546                 $kid_chg->inventory(inventory_cb => $kid_cb);
547             },
548             errsub => undef,
549             parent_cb => $steps->{'all_kids_done_cb'},
550         );
551     };
552
553     step all_kids_done_cb => sub {
554         my ($kid_results) = @_;
555         if (grep { defined($_->[0]) } @$kid_results) {
556             # we have errors, so collect them and make a "combined" error.
557             my @annotated_errs;
558             for my $i (0 .. $self->{'num_children'}-1) {
559                 my $kr = $kid_results->[$i];
560                 next unless defined($kr->[0]);
561                 push @annotated_errs,
562                     [ $self->{'child_names'}[$i], $kr->[0] ];
563             }
564             return $self->make_combined_error(
565                 $params{'inventory_cb'}, [ @annotated_errs ]);
566         }
567
568         my $inv = $self->_merge_inventories($kid_results);
569         if (!defined $inv) {
570             return $self->make_error("failed", $params{'inventory_cb'},
571                     reason => "notimpl",
572                     message => "could not generate consistent inventory from aggregate child changers");
573         }
574
575         $params{'inventory_cb'}->(undef, $inv);
576     };
577
578 }
579
580 sub set_meta_label {
581     my $self = shift;
582     my %params = @_;
583     my $state;
584
585     return if $self->check_error($params{'finished_cb'});
586
587     my $finished_cb = $params{'finished_cb'};
588     my $orig_slot = $params{'slot'};
589
590     if (!defined $params{'slot'}) {
591         return $self->make_error("failed", $finished_cb,
592             reason => "invalid",
593             message => "no 'slot' params set.");
594     }
595
596     if (!defined $params{'meta'}) {
597         return $self->make_error("failed", $finished_cb,
598             reason => "invalid",
599             message => "no 'meta' params set.");
600     }
601
602     my ($kid, $slot) = split(':', $orig_slot, 2);
603     my $child = $self->{'children'}[$kid];
604     if (!defined $child) {
605         return $self->make_error("failed", $finished_cb,
606             reason => "invalid",
607             message => "no changer $kid");
608     }
609
610     $params{'slot'} = $slot;
611     return $child->set_meta_label(%params);
612 }
613
614 sub get_meta_label {
615     my $self = shift;
616     my %params = @_;
617     my $state;
618
619     return if $self->check_error($params{'finished_cb'});
620
621     my $finished_cb = $params{'finished_cb'};
622     my $orig_slot = $params{'slot'};
623
624     if (!defined $params{'slot'}) {
625         return $self->make_error("failed", $finished_cb,
626             reason => "invalid",
627             message => "no 'slot' params set.");
628     }
629
630     my ($kid, $slot) = split(':', $orig_slot, 2);
631     my $child = $self->{'children'}[$kid];
632     if (!defined $child) {
633         return $self->make_error("failed", $finished_cb,
634             reason => "invalid",
635             message => "no changer $kid");
636     }
637
638     $params{'slot'} = $slot;
639     return $child->get_meta_label(%params);
640 }
641
642 # Takes keyword parameters 'oksub', 'errsub', 'parent_cb', and 'args'.  For
643 # each child, runs $oksub (or, if the child is "ERROR", $errsub), passing it
644 # the changer, an aggregating callback, and the corresponding element from
645 # @$args (if specified).  The callback combines its results with the results
646 # from other changers, and when all results are available, calls $parent_cb.
647 #
648 # This forms a kind of "AND" combinator for a parallel operation on multiple
649 # changers, providing the caller with a simple collection of the results of
650 # the operation. The parent_cb is called as
651 #   $parent_cb->([ [ <chg_1_results> ], [ <chg_2_results> ], .. ]).
652 sub _for_each_child {
653     my $self = shift;
654     my %params = @_;
655     my ($oksub, $errsub, $parent_cb, $args) =
656         ($params{'oksub'}, $params{'errsub'}, $params{'parent_cb'}, $params{'args'});
657
658     if (defined($args)) {
659         confess "number of args did not match number of children"
660             unless (@$args == $self->{'num_children'});
661     } else {
662         $args = [ ( undef ) x $self->{'num_children'} ];
663     }
664
665     my $remaining = $self->{'num_children'};
666     my @results = ( undef ) x $self->{'num_children'};
667     my $maybe_done = sub {
668         return if (--$remaining);
669         $parent_cb->([ @results ]);
670     };
671
672     for my $i (0 .. $self->{'num_children'}-1) {
673         my $child = $self->{'children'}[$i];
674         my $arg = @$args? $args->[$i] : undef;
675
676         my $child_cb = sub {
677             $results[$i] = [ @_ ];
678             $maybe_done->();
679         };
680
681         if ($child eq "ERROR") {
682             if (defined $errsub) {
683                 $errsub->("ERROR", $child_cb, $arg);
684             } else {
685                 # no errsub; just call $child_cb directly
686                 $child_cb->(undef) if $child_cb;
687             }
688         } else {
689             $oksub->($child, $child_cb, $arg) if $oksub;
690         }
691     }
692 }
693
694 sub _merge_inventories {
695     my $self = shift;
696     my ($kid_results) = @_;
697
698     my @combined;
699     my $nb = 0;
700     for my $kid_result (@$kid_results) {
701         my $kid_inv = $kid_result->[1];
702
703         for my $x (@$kid_inv) {
704             my $slotname = "$nb:" . $x->{'slot'};
705             my $current = $slotname eq $self->{'current_slot'};
706             push @combined, {
707                     state => $x->{'state'},
708                     device_status => $x->{'device_status'},
709                     f_type => $x->{'f_type'},
710                     label => $x->{'label'},
711                     barcode => $x->{'barcode'},
712                     reserved => $x->{'reserved'},
713                     slot => $slotname,
714                     import_export => $x->{'import_export'},
715                     loaded_in => $x->{'loaded_in'},
716                     current => $current,
717                 };
718         }
719         $nb++;
720     }
721
722     return [ @combined ];
723 }
724
725 package Amanda::Changer::aggregate::Reservation;
726 use vars qw( @ISA );
727 @ISA = qw( Amanda::Changer::Reservation );
728
729 sub new {
730     my $class = shift;
731     my ($chg, $kid_res, $slot) = @_;
732     my $self = Amanda::Changer::Reservation::new($class);
733
734     $self->{'chg'} = $chg;
735     $self->{'kid_res'} = $kid_res;
736     $self->{'device'} = $kid_res->{'device'};
737     $self->{'barcode'} = $kid_res->{'barcode'};
738     $self->{'this_slot'} = $slot;
739
740     return $self;
741 }
742
743 sub do_release {
744     my $self = shift;
745     my %params = @_;
746
747     # unref the device, for good measure
748     $self->{'device'} = undef;
749
750     $self->{'kid_res'}->release(%params);
751     $self->{'kid_res'} = undef;
752 }
753
754 sub get_meta_label {
755     my $self = shift;
756     my %params = @_;
757
758     $self->{'kid_res'}->get_meta_label(%params);
759 }
760
761 sub set_meta_label {
762     my $self = shift;
763     my %params = @_;
764
765     $self->{'kid_res'}->set_meta_label(%params);
766 }